Skip to content

Document the account configuration endpoints - #70

Open
danielpaul wants to merge 19 commits into
mainfrom
claude/api-endpoints-docs-vflvf9
Open

Document the account configuration endpoints#70
danielpaul wants to merge 19 commits into
mainfrom
claude/api-endpoints-docs-vflvf9

Conversation

@danielpaul

@danielpaul danielpaul commented Jul 30, 2026

Copy link
Copy Markdown
Member

Documents the endpoints added in IdentitySquare/CookieChimp#2554, which put the dashboard's account-configuration surface on the v1 API.

This description has been rewritten as the PR changed. The original listed TCF vendor operations, since removed at @daniel's request.

21 new operations, each with a reference page and a nav entry, plus 27 new schemas:

Resource Operations
/services list, create, get, update, delete
/consent-banners list, create, get, update, duplicate, delete
/consent-banners/from-template create from a jurisdiction template
/banner-templates list
/compliance get the regional compliance report
/scans list, create, get
/integrations list, get, update
/accounts/{id} update

Three things the spec has to say that it didn't before

A 403. Configuration changes are admin-only, mirroring the dashboard — a group role supersedes a direct membership, and account keys carry the account's own authority. AccountAdminRequired is a new reusable response, declared on the operations that can return it.

Choosing an account. A user API key can reach several accounts. Endpoints that accept account_id let you name one; for writes it is required when the key can reach more than one, otherwise the request would act on an account it picked rather than one you named (AmbiguousAccount, a 400). The parameter is declared only on the endpoints that honour it — the rest resolve an account on their own.

Asynchronous work. A scan is created queued and its report appears only once status reaches completed, so the scan endpoints say to poll.

Both new failure modes are in the consolidated errors table too, which previously defined 400 as an invalid page parameter alone and did not mention 403 at all.

Read and write now describe the same thing

Review kept finding fields the API accepted but never documented as readable, or returned but never documented as writable. Rather than patching each one:

  • The banner write schema listed 18 of the 49 settings the designer exposes and waved at the rest. It is generated from the model's columns and enums now, and the read schema composes it — 55 effective properties, nothing writable that cannot be read back.
  • The account schema gained the twelve settings PATCH accepts. allowed_domains_list stays write-only and says so; its read counterpart is the parsed allowed_domains array.
  • Translation attributes are reconciled against ConsentBannerPolicy: the permit list, request schema and response schema agree, with nothing missing or spurious on either side. Localized request fields are nullable, since the response returns null for unset copy.

Behaviour documented on the operation rather than left to be discovered

  • Banners are always created disabled and inherit the last-updated banner's design.
  • Translations are not accepted when creating a banner — add them by updating it, which is where the language entitlement is enforced. Create and update have separate request schemas so a generated client only offers what each accepts.
  • Integrations are backfilled on first listing, and only those the account's platform can run.
  • Only one scan runs at a time per account, accounts have a daily limit, and mobile app accounts cannot be scanned — all 422.
  • category_id is required when creating a service; supplying it on update moves the service.
  • target_canadian_regions and target_us_states share a column, so a Canadian-targeted banner reports its provinces under the former.
  • GET /banner-templates is the one listing without pagination — a fixed catalogue, not a collection of the account's records.
  • Two endpoints take an unwrapped body: POST /auth and POST /consent-banners/from-template.
  • The wrapper table covers every endpoint, including /servicessite_service, which does not match its path.

Verification

  • npx @redocly/cli lint — passes (one pre-existing info-license warning, untouched)
  • Semantic diff against main: 0 operations, schemas, parameters or responses lost; 21 operations and 27 schemas added. Worth stating because GitHub renders a large deletion count — that is the diff algorithm reacting to a big block inserted before components:, not content loss.
  • All 53 operations have a reference page; every docs.json page resolves; every MDX page maps to a real operation.
  • Request schemas checked against a JSON Schema validator where create and update differ, and where allOf composition is involved — a full update payload carrying targeting, a logo and a nested translation with a null field validates.

🤖 Generated with Claude Code

https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp

Covers the endpoints added in CookieChimp#2551, which put the dashboard's
account configuration surface on the v1 API: services, consent banners, TCF
vendors, scans, integrations, and updating an account's settings.

Twenty-two new operations, each with a reference page and a nav entry, plus
the schemas for the five new resources.

Two things the spec now has to say that it didn't before:

- A 403. Editing account settings is admin-only on the web, and the API
  mirrors that: an account key carries the account's own authority, but a user
  key only does when that user is an admin. AccountAdminRequired is a new
  reusable response.
- Asynchronous work. A scan is created queued and its report only appears once
  the status reaches completed, so the scan endpoints say to poll rather than
  expect a report on the create response.

Behaviour that would otherwise surprise a caller is documented on the
operation rather than left to be discovered: banners are always created
disabled and inherit the last-updated banner's design, integrations are
backfilled on first listing, re-adding a removed TCF vendor restores the
original record, and only one scan runs at a time per account.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp
Copilot AI review requested due to automatic review settings July 30, 2026 13:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copy link
Copy Markdown
Member Author

copilot-pull-request-reviewer is the only check on this repo and it failed, but not on this diff — the job log ends in a billing error, not a review:

errorType: 'quota',
statusCode: 402,
[cause]: [Error: You have exceeded your monthly quota]

It failed identically on CookieChimp#2554 at the same time, ~40 seconds in and before producing any review comments. Nothing to fix by pushing; the Copilot quota needs topping up (or the reviewer disabling) at the account level.

Since this repo has no other CI, the verification I can offer is what I ran locally: redocly lint passes, and a semantic diff against main confirms no operation, schema, parameter or response was lost — details in the PR description.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 30384f525a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openapi.yaml
POST and PUT shared one schema, and it required only name. The prose said
category_id was required on create, but a generated client would have accepted
{"site_service": {"name": "Example"}} and got a 422 back from the API.

Split the two: create requires name and category_id, update requires neither,
since the controller permits a partial update and supplying category_id there
is what moves the service to another category.

Checked against a JSON Schema validator: a create with only a name is now
rejected, a create with both is accepted, and a partial update still is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp

Copy link
Copy Markdown
Member Author

Good catch, and it was the one place the spec disagreed with the API. Fixed in 0e7b327.

POST /services and PUT /services/{id} shared SiteServiceWriteRequest, which required only name — so a generated client would happily send {"site_service": {"name": "Example"}} and get a 422 back from the controller, which needs a category_id to build the service under.

Split into SiteServiceCreateRequest (requires name and category_id) and SiteServiceUpdateRequest (requires neither). Update deliberately requires nothing: the controller permits a partial update, and category_id there is what moves a service to another category rather than being mandatory.

Verified against a JSON Schema validator rather than just re-reading it:

CREATE, name only        -> rejected ('category_id' is a required property)
CREATE, name+category_id -> accepted
UPDATE, partial          -> accepted

I checked the other new write schemas for the same shared-schema problem and they're fine — /tcf-vendors is create-only, /integrations and PATCH /accounts/{id} are update-only, and consent banners and scans genuinely have no required fields (a banner name is generated if omitted, and a scan defaults to the account's own domain).

Redocly lint still passes.


Generated by Claude Code

claude added 2 commits July 30, 2026 13:51
Follows the code changes in CookieChimp#2554 made in response to review.

Configuration mutations are admin-only, so the eleven mutating operations now
declare a 403 alongside account update, and AccountAdminRequired says what
earns it: an account key carries the account's own authority, a user key needs
an admin role, and a group role supersedes a direct membership. Reads are
unaffected.

Contract changes:

- A banner's `languages` array of codes becomes `translations`, each with an
  `id`. The id is what you send back in `banner_languages_attributes`, and
  without one a client cannot update a translation it created.
- `privacy_policy_url` is gone from the account schema. It is an attr_accessor
  consumed when default banners are built at creation, so update reported
  success and stored nothing.
- Banner creation no longer accepts translations; they are added by updating
  the banner, which is where the language entitlement is enforced.
- The integrations listing is platform-aware — a mobile app account does not
  see website-only integrations.
- Scanning is website-only, so a mobile app account gets a 422.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp
Follows CookieChimp#2554. TCF vendor management comes out of the API for now,
so its four operations, schemas, reference pages and nav group are removed.

Adds three operations:

- GET /compliance — the regional compliance report, with per-regulation issues,
  warnings and satisfied requirements. Ignored regulations are reported
  separately and excluded from the top-level verdict.
- GET /banner-templates — jurisdiction templates grouped by region.
- POST /consent-banners/from-template — a banner preconfigured for a
  jurisdiction.

Also a new 400. A user API key that can reach more than one account must name
the account it means when writing, since the alternative is a write landing on
a silently chosen one; AmbiguousAccount documents that and is declared on the
nine write operations it can affect.

53 operations, each with a reference page and a nav entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ba1cc9931b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openapi.yaml
Comment thread openapi.yaml
Two gaps from review, both in what the last commit documented rather than in
the API itself.

The 400 told callers to pass account_id, but no operation declared the
parameter and none existed to reference — so a generated client had no way to
send the selector the error asks for, leaving those writes unusable for a
multi-account user key. Adds a reusable account_id query parameter and
references it from all 43 account-scoped operations, reads included: the
selector is resolved for any request made with a user key, not only writes.

POST /consent-banners/from-template takes jurisdiction_id at the top level,
while the spec said every resource endpoint wraps its body and named POST /auth
as the only exception. Documented as a second exception rather than wrapped:
jurisdiction_id selects which template to build from and is not an attribute of
the banner being created, so a consent_banner wrapper would misrepresent it.
Both statements of the contract — the spec description and the authentication
page — now name both exceptions, and the authentication page explains how to
choose an account.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp

Copy link
Copy Markdown
Member Author

Both valid, fixed in 9dbb405. Both were gaps in what I documented last commit rather than in the API itself.

account_id was undeclared. The 400 told callers to pass it, but no operation declared the parameter and none existed to reference — so a generated client had no way to send the selector the error demands. That made those writes genuinely unusable for a multi-account user key, which is the opposite of what the error was trying to achieve.

Added a reusable parameter and referenced it from all 43 account-scoped operations, not only the 10 that can return the 400 — resolve_account_selector! runs for any request made with a user key, so it narrows reads too, and documenting it only on writes would have been the same half-truth in a smaller form. Verified mechanically:

account-scoped operations: 43
missing account_id: []          duplicated: []
ops returning AmbiguousAccount: 10 | without the parameter: []

The wrapper contract — I documented from-template as a second exception rather than wrapping it. jurisdiction_id selects which template to build from; it isn't an attribute of the banner being created, so a consent_banner wrapper would misrepresent it, and it matches what the server actually reads. Both statements of the contract now name both exceptions — the spec description and authentication.mdx, which had its own copy that would otherwise have drifted.

While there I added a short "Choosing an account" section to the authentication page, since the parameter is only discoverable from individual operations otherwise.

Redocly lint passes.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9dbb40598e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openapi.yaml Outdated
Comment thread openapi.yaml
…rgeting

Follows the code changes in CookieChimp#2554.

The account_id selector is now opt-in per controller rather than global, so it
is declared only on the endpoints that honour it — services, consent banners,
banner templates, compliance, scans and integrations. Removed from the 23
legacy operations that never accepted it; those keep resolving an account on
their own, and none of them can return the ambiguous-account 400.

The banner write schema gains banner_languages_attributes. The create operation
tells callers to add translations by updating the banner, but the schema
described no way to do it, so the generated reference and typed clients had
nothing to show. Documents adding, updating by id, and discarding.

Both banner schemas gain target_canadian_regions. Canadian provinces share the
target_us_states column behind a virtual accessor, so a Canadian-targeted
banner previously reported QC and ON as US states and never returned the field
that accepts them — its targeting could not be round-tripped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp

Copy link
Copy Markdown
Member Author

Both addressed in 071c5a4, though the first one resolved in the opposite direction to what was suggested.

Ambiguous-account on legacy writes — rather than adding the 400 to those eleven operations, the code changed: the account_id selector is now opt-in per controller (IdentitySquare/CookieChimp#2554). Applying it globally had quietly widened a pre-existing authorization gap on the older controllers, so it's now enabled only on the endpoints this PR adds. Those legacy operations never honoured the selector and can't return the 400, so documenting one would have been fiction. Went the other way instead: removed account_id from the 23 legacy operations it had been added to last commit.

ops with account_id: 20        outside opted-in paths: []
ops with AmbiguousAccount: 10  outside opted-in: []

Translation attributes — correct and a real hole: the create operation told callers to add translations by updating the banner, and the write schema showed no way to do it. Added banner_languages_attributes, documenting all three operations (add by omitting id, update by passing the id from the banner's translations array, discard via discarded_at).

Also added target_canadian_regions to both banner schemas while in there. Canadian provinces share the target_us_states column behind a virtual accessor, so a Canadian-targeted banner was reporting QC and ON as US states and never returning the field that accepts them — its targeting couldn't be round-tripped. Fixed in the API too.

Redocly lint passes.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 071c5a45f7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openapi.yaml Outdated
Comment thread api-reference/authentication.mdx Outdated
Both from review, and both fallout from scoping the account selector in the
last commit.

POST and PUT /consent-banners shared one schema, which since the last commit
advertised banner_languages_attributes — a field create does not accept, and
says so in the same breath. A generated client would have offered a payload the
API ignores. Create now takes ConsentBannerCreateRequest and update takes
ConsentBannerUpdateRequest, which composes the shared write schema with the
translation attributes. Checked against a JSON Schema validator: an update
carrying translations validates, and both plain payloads still do.

The parameter description and the authentication page both said writes require
account_id, full stop. That was true when the selector was global; now that it
is declared per endpoint, it binds only the operations that accept it — the
rest resolve an account on their own, as the sentence immediately before it
already said. Qualified both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp

Copy link
Copy Markdown
Member Author

Both correct, fixed in 1c5ac04. Both are fallout from my own previous two commits rather than new ground.

Translations in the create schema — I added banner_languages_attributes to the shared write schema last commit, which meant POST /consent-banners advertised a field it doesn't accept while its own description said so. Same create/update split as SiteService earlier in this PR; I should have applied the lesson the first time. Create now takes ConsentBannerCreateRequest, update takes ConsentBannerUpdateRequest, which composes the shared schema with the translation attributes. Verified rather than assumed, since allOf with sibling properties is easy to get subtly wrong:

UPDATE with translations -> valid
CREATE plain             -> valid
UPDATE plain             -> valid

The selector requirement was overclaimed — "writes require account_id" was true when the selector was global, but it went per-endpoint in the commit before last and I didn't revisit the prose. It contradicted the sentence directly above it, which already said endpoints without the parameter resolve an account on their own. Qualified in both places it appears: the parameter description and the authentication page.

Redocly lint passes.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1c5ac046a5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openapi.yaml
The guide said the wrapper matches the resource and listed six of them. Six
more were added by this PR, and one of those does not follow the rule the
sentence states: /services wraps in site_service, not service. A caller
following the guide would have sent the wrong shape.

Replaces the prose list with a table covering every endpoint that takes a
wrapped body, and says outright that the wrapper does not always match the
path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp

Copy link
Copy Markdown
Member Author

Correct, and a good catch — fixed in 5740b59.

The guide said "the wrapper matches the resource" and listed six. This PR added six more, and one of them breaks the rule the sentence states: /services wraps in site_service, not service. Anyone hand-writing a request from the guide would have got the shape wrong, with the guide actively pointing them at the wrong answer.

Replaced the prose list with a table covering every endpoint that takes a wrapped body, and said outright that the wrapper doesn't always match the path rather than leaving site_service to look like a typo.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5740b591c4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openapi.yaml
Both envelope statements said list endpoints return
{"pagination": ..., "data": [...]}, and the pagination section said list
endpoints accept page and per_page. GET /banner-templates does neither: it is a
fixed catalogue of jurisdiction templates rather than a collection of the
account's records, so it returns data alone and takes no pagination parameters.
A client generated against the stated contract could have required a pagination
object that is never sent.

Checked the rest of the spec for the same mismatch — it is the only listing
without pagination.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp

Copy link
Copy Markdown
Member Author

Correct — fixed in 18bfa8d.

GET /banner-templates is a fixed catalogue of jurisdiction templates rather than a collection of the account's own records, so it returns data alone and accepts neither page nor per_page. But three places said otherwise: both envelope statements and the pagination section, which claimed list endpoints support those parameters. A client generated against that contract could have required a pagination object that is never sent.

All three now name the exception. I also checked the rest of the spec for the same mismatch rather than fixing only what was reported:

LIST WITHOUT PAGINATION: GET /banner-templates (per_page param: False)

That's the only one — /compliance returns a single object, which is already the documented single-resource shape.

Redocly lint passes.


Generated by Claude Code

Follows CookieChimp#2554.

The account response documented four fields while PATCH accepts twelve, so a
client could write default_language, translations, industry, description,
support_email, banner_debug_mode, blocking_mode, retain_consent_duration,
domain or subdomain and had no way to read any of them back. All are in the
response schema now. allowed_domains_list stays write-only and says so — the
read side returns the parsed allowed_domains array.

Banner translations carried an id and a language code while the update endpoint
accepts seventeen localized fields. The copy is documented alongside the id, so
the reference shows what can be read before it is overwritten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 20049b592d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openapi.yaml Outdated
The banner update schema listed twelve of the twenty attributes Rails permits
under banner_languages_attributes, so eight supported edits were missing:
consent_additional_description, banner_footer_content, settings_more_info_title,
settings_more_info, do_not_sell_title, do_not_sell_description,
preferences_accept_btn_text and preferences_accept_necessary_btn_text. The
response was expanded to return all of them last commit, which left the
reference telling clients to read fields it never showed them how to write.

Reconciled against ConsentBannerPolicy rather than by inspection — the write
schema, the read schema and the permit list now agree with nothing missing and
nothing spurious on either side, and every field the API returns is one it
accepts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp

Copy link
Copy Markdown
Member Author

Correct, and exactly those eight — fixed in 91f4521.

Expanding the response last commit left the reference telling clients to read fields it never showed them how to write. The write schema documented twelve of the twenty attributes ConsentBannerPolicy permits.

Rather than adding the eight named fields and calling it done — which is how this gap opened in the first place — I reconciled all three surfaces against the policy itself:

write vs Rails  -> missing: [] | extra: []
read vs Rails   -> missing: [] | extra: []
every returned field is writable: True

So the permit list, the request schema and the response schema now agree with nothing missing and nothing spurious on either side. discarded_at is write-only by design (it's how you remove a translation) and correctly absent from the response.

Redocly lint passes.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 91f4521301

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openapi.yaml Outdated
Comment thread api-reference/authentication.mdx
The banner write schema listed eighteen of the forty-nine settings the designer
exposes and waved at the rest with "any other attribute the designer exposes is
accepted too". That was tolerable while the response returned a subset as well;
now that CookieChimp#2554 returns all of them, it left the reference describing
neither side properly. Generated from the model's columns and enums, so the
listed types match what the API sends.

The read schema now composes the write schema rather than restating part of it,
which mirrors how the response is built — 55 effective properties, with nothing
writable that cannot be read back.

ConsentBannerUpdate had allOf beside a sibling properties block, which left its
own example failing validation because the inherited fields counted as
unevaluated. Both branches sit inside the allOf now, and a full update payload
carrying targeting, a logo and a nested translation validates.

Translation update fields accept null. The response returns null for unset
localized copy, so string-only request properties meant the object could not be
sent back as received.

The errors table defined 400 as an invalid page parameter alone and did not
mention 403 at all, while this PR added an account-ambiguity 400 and an
admin-only 403. Both are in the table now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp

Copy link
Copy Markdown
Member Author

Both valid, fixed in d291d13 — along with a larger gap they led me to.

Nullable translation fields — the response returns null for unset localized copy while the request accepted only strings, so the object couldn't be sent back as received. id and language_code stay non-nullable since they identify the record.

The errors table defined 400 as an invalid page parameter alone and didn't mention 403 at all, while this PR added an account-ambiguity 400 and an admin-only 403. Both are in the consolidated table now — it's the page readers actually reach for, and it contradicted the sections above it.

What these exposed. Checking the nullable claim, I found the write schema documented eighteen of the forty-nine banner settings and waved at the rest with "any other attribute the designer exposes is accepted too". That was survivable while the API also returned a subset; now that it returns all forty-nine (CookieChimp#2554), the reference described neither side. Generated the full list from the model's columns and enums, and the read schema now composes the write schema rather than restating part of it:

read effective properties: 55
writable but not readable: []

That restructuring also surfaced a modelling bug of mine: ConsentBannerUpdate had allOf beside a sibling properties block, which made its own example fail validation because inherited fields counted as unevaluated. Both branches sit inside the allOf now, and a full update payload — targeting, a logo, a nested translation with a null field — validates.

Redocly is back to the single pre-existing info-license warning.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d291d13cb1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openapi.yaml
Comment thread openapi.yaml
Two regressions from generating the write schema last commit.

The generator only emitted attributes the policy lists as bare symbols, so
target_countries, target_us_states, target_canadian_regions and button_colors
were dropped — the arrays a targeting update actually needs, and which the
update example itself sends. All four are back, and the schema is now checked
against the policy rather than against the read schema it composes, which made
the previous check tautological.

ConsentBanner declared those fields nullable in a second allOf branch, which
does not override the string-only definition inherited from the first: every
branch must validate, so a response carrying a null consent_title violated its
own schema. The nullable fields are nullable in the shared definition instead,
derived from which columns the database leaves nullable, and the conflicting
branch is gone.

Checked with a JSON Schema validator on real shapes: a response with null
localized copy validates, and so does an update carrying targeting, a colour
map and a nested translation with a null field.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a15f2eea8f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread api-reference/authentication.mdx Outdated
The 403 row described only a non-admin attempting an admin-only change, while
GET /compliance returns 403 when the account's plan excludes the report — a
read-time entitlement failure a reader would have misdiagnosed from the table.

Rather than fix that row alone, enumerated every response the spec declares and
reconciled the table against it, which turned up two more:

- 401 also covers the wrong email or password on log in, not just a bad or
  absent API token.
- 422 also covers a setting the plan does not allow. That one is mine from this
  round: CookieChimp#2554 started refusing scheduled scanning on free accounts
  and I documented the behaviour nowhere. The integration update operation now
  says so, including that turning the feature off always works.

404 gains the cross-account case, which several operations already described
individually.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp

Copy link
Copy Markdown
Member Author

Correct — fixed in 6685e48, along with two more the same check turned up.

The 403 row described only a non-admin attempting an admin-only change, while GET /compliance returns 403 when the plan excludes the report. A reader would have misdiagnosed a read-time entitlement failure as a permissions one. That row is mine from last round, so this is the second pass over the same table.

Rather than fix the reported row alone — which is the habit that produced several of these rounds — I enumerated every response the spec declares and reconciled the table against all of them:

  • 401 also covers the wrong email or password on log in, not just a bad or absent API token.
  • 422 also covers a setting the plan disallows. That one is mine from this round: CookieChimp#2554 started refusing scheduled scanning on free accounts and I documented it nowhere — not on the operation, not in the table. The integration update operation now says so, including that turning the feature off always works so a downgraded account isn't stuck with it on.
  • 404 gains the cross-account case, which several operations already described individually but the summary didn't.

Redocly lint passes.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6685e48974

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openapi.yaml Outdated
claude added 2 commits July 30, 2026 15:48
POST /consent-banners forces a new banner disabled, but the create request
reused the shared write schema and so advertised `enabled`. A generated client
would have offered it, and a caller could have sent `enabled: true` and been
quietly overridden — the operation said as much in prose while the schema said
otherwise.

Create now composes the write schema with `enabled` excluded. Checked with a
JSON Schema validator: a create carrying `enabled` is rejected, one without it
validates, and update still accepts it, which is how a banner goes live.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp
The status is derived from the visitors database, which can be down while
the account itself reads and writes fine. The API now answers null rather
than failing the response or guessing a status from the branches it can
still evaluate — say so, and say that null is not a downgrade.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BAKTdSBroe771g8CYkGJp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8ebe8dd9eb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openapi.yaml Outdated
…jects

The services endpoints accept and return localized copy now, so describe
the nested attributes and the translations the response carries.

ConsentBannerCreate removed `enabled` with `not: {}`, which does not
remove a property — tooling that flattens allOf still advertised it, and
strictly it made any payload carrying `enabled` invalid when the API in
fact accepts it and ignores the value. The settings are their own schema
now, `enabled` composes on top for update and read, and create refers to
the settings alone.

Copy link
Copy Markdown
Member Author

Two changes in f888c9f, following IdentitySquare/CookieChimp#2554.

not: {} does not remove a property — and it forbade something the API accepts. ConsentBannerWrite does declare enabled, so tooling that flattens allOf kept advertising it; and as a schema, enabled: {not: {}} makes any payload carrying enabled invalid, while the API in fact takes it and ignores the value:

POST /consent-banners  {"consent_banner": {"name": "X", "enabled": true}}
  -> 201, enabled: false

The settings are their own schema now — ConsentBannerSettings, 52 properties — with enabled composed on top in ConsentBannerWrite for update and read, and ConsentBannerCreate referring to the settings alone. enabled is absent from create rather than present-and-forbidden, and sending it is documented as ignored rather than rejected.

Service translations. The services endpoints now accept site_service_translations_attributes and return translations, so both are described: the nested attributes with how to add, change and discard an entry, and the response array with the IDs those attributes expect back. Noted that, unlike banner translations, these carry no plan entitlement and so can be supplied when creating a service.

Also in 8ebe8dd: onboarding_status is type: [string, "null"], since the API answers null when the analytics store behind it is unreachable. The description says what null means — unknown, not a downgrade — and the filter parameter says those accounts drop out of a filtered listing.

Redocly lint passes; the one info-license warning is pre-existing.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f888c9f1dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openapi.yaml Outdated
…eate

A nested entry without an id creates a translation, and cannot be created
without naming its language — so require one of id or language_code
rather than leaving both optional, on service translations and banner
translations alike.

Service translations are no longer accepted when creating a service, for
the same reason banner translations are not: the language entitlement is
enforced on update. Split out of the create schema the way `enabled` was,
so create does not advertise a field it ignores.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6f408e8728

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openapi.yaml Outdated
The response nested target_type and the region arrays under a `targeting`
object while the update endpoint takes them at the top level, so the one
part of a banner a client could not read and send back unchanged was its
targeting. They are flat now, on both sides — the read schema already
inherits them from the settings it composes.

Copy link
Copy Markdown
Member Author

Round 20 — one finding, fixed in f02e047 (API side in IdentitySquare/CookieChimp@7577e8a).

Targeting was the one thing a client could not read and send back. Correct, and it undercut the round-trip this PR's description claims. The response grouped target_type and the three region arrays under a targeting object; update takes them at the top level like every other setting.

Fixed on the API side rather than documented as a transformation — the grouping bought nothing the flat shape doesn't. It existed because Canadian provinces are stored in the target_us_states column behind a target_canadian_regions accessor, and reporting QC and ON as US states was a real defect; but splitting them correctly never required nesting them. The response is flat now and still reports provinces under the field that accepts them, with specs covering both directions.

On the spec side that means simply deleting the targeting object — ConsentBanner composes ConsentBannerSettings, which already declares all four fields, so the read schema exposes them without anything new. Confirmed by resolving the composition: targeting gone, all four present.

Earlier in this round, also in 6f408e8: anyOf: [required: [id], required: [language_code]] on both nested translation schemas, and SiteServiceCreate split from the translations it now ignores.

Redocly lint clean; the API suite is 314 examples, 0 failures.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants