Skip to content

Management API: typed clients generated from the OpenAPI specs - #2

Merged
sylvesterdamgaard merged 8 commits into
mainfrom
feat/management-client
Oct 8, 2026
Merged

sylvesterdamgaard merged 8 commits into
mainfrom
feat/management-client

Conversation

@sylvesterdamgaard

@sylvesterdamgaard sylvesterdamgaard commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Adds cbox_id.management: typed clients for Cbox ID's four management planes, generated from the server's OpenAPI documents (cbox-id integration/wave-7). It is the Python twin of id-js's management client (feat/management-client) and matches its behaviour.

What's in it

  • EnvironmentClient, WorkspaceClient, PlatformClient, AccountClient. Method names come from x-action, e.g. env.apps.secrets.rotate(id, body) and env.sso.saml_metadata.import_(...). Bodies, queries and data are TypedDicts, and the 253 generated operations pass mypy --strict.

  • Specs are vendored from cbox-id integration/wave-7 in openapi/. They include the typed answers (ManagementKey with a nullable token on replay, LegacyLoginProbe, ManifestSync, SelfServiceSignup, VerificationResend), webhook signature_scheme and webhooks.signature_scheme.change, log stream options and health, the log_streams.test failure kind, and organizations.portal_links.list / revoke. python -m scripts.generate_management regenerates the code, --check fails when the generated code is stale, and --fetch plane=url refreshes a spec from a running server. A drift test fails when the generated code and the specs disagree, and a surface snapshot catches API changes caused by generator edits.

  • Runtime behaviour, same as id-js:

    • keys are checked against their plane
    • access_token can be a string or a callable
    • environment= sends Cbox-Environment for root tokens
    • every write gets an Idempotency-Key, and retries reuse it while honouring Retry-After
    • replayed reflects Idempotent-Replayed
    • approval flow: a 202 calls on_approval_required, the client polls poll_url (same origin only) and repeats with Cbox-Approval
    • ApprovalDeniedError / ApprovalExpiredError
    • approval="return" returns a PendingApprovalResult with resume()
    • CboxIdApiError takes request_id from the body first, then X-Request-Id
    • ManagementNetworkError
    • DPoP proofs with nonce retry
    • …_all iterators for paged lists
  • Audit Logs helpers: AuditLogger (batches of up to 100, one idempotency key per batch, a failed batch stays queued with its key), export_audit_logs(), and verify_audit_chain() / verify_audit_log_chain(). canonical_json() reproduces the server's PHP quirks and is checked against id-js's server-computed vector (tests/fixtures/audit_chain.json).

  • verify_standard_webhook() sits beside verify_webhook(). It verifies endpoints on the standard_webhooks scheme (webhook-id / webhook-timestamp / webhook-signature) with a whsec_ secret or the endpoint's hex Cbox secret, and it is tested against the Standard Webhooks spec vector.

  • README: the install line now names v0.9.0.

Decisions

  • Sync only, on httpx, because the rest of the SDK is sync.
  • Overloads on approval: the default call is typed ApiResponse[T], and approval="return" is typed ApiResponse[T] | PendingApprovalResult[T].
  • New runtime dependency typing-extensions>=4.7, needed for NotRequired on 3.10. pyyaml and types-PyYAML are dev-only.
  • Generated files are excluded from ruff format and E501, so the drift test compares bytes. The management tests are added to mypy's file list so the assert_type checks run.

Gate (run locally on Python 3.14; CI covers 3.10–3.13): ruff check ., ruff format --check ., mypy, pytest all pass (209 tests), and python -m scripts.generate_management --check is clean. No version bump.

Note: the wave-7 specs still carry no x-scope / x-danger on 45 actions (35 environment, 10 workspace; examples: apps.list, apps.create, invitations.send, environments.create). Their scope still comes through from the description's "Requires scope" line, but their danger is None in the operation tables.

The four specs (environment, workspace, platform, account) come from cbox-id
integration/wave-6. scripts/generate_management.py turns them into typed
modules; --check fails when the output is stale and --fetch refreshes a spec
from a running server. typing-extensions becomes a runtime dependency for
NotRequired/TypedDict on 3.10; pyyaml is dev-only.
EnvironmentClient, WorkspaceClient, PlatformClient and AccountClient, generated
from the vendored specs and named after the server's actions. A hand-written
transport carries the credential checks, Idempotency-Key reuse across retries
(Retry-After honoured), the approval loop with same-origin polling and a
return-pending mode, Cbox-Environment for root tokens, DPoP proofs, paging,
and CboxIdApiError / ManagementNetworkError. Mirrors id-js's management client.

Includes the Audit Logs helpers (buffered logger, export poller, chain
verifier byte-compatible with the server's canonical JSON).
Typed answers (ManagementKey with a nullable token on replay, LegacyLoginProbe,
ManifestSync, SelfServiceSignup, VerificationResend), webhook signature_scheme
and webhooks.signature_scheme.change, log stream options and health, the
log_streams.test failure kind, and organizations.portal_links.list/revoke.
More routes now declare 202 approval_required, so their methods take
approval=.
verify_standard_webhook() checks webhook-id / webhook-timestamp /
webhook-signature for endpoints on the standard_webhooks scheme, with a whsec_
secret or the endpoint's hex Cbox secret, and is tested against the
specification's vector. README: install line names v0.9.0.
@sylvesterdamgaard
sylvesterdamgaard merged commit eb8dd66 into main Oct 8, 2026
4 checks passed
@sylvesterdamgaard
sylvesterdamgaard deleted the feat/management-client branch October 8, 2026 16:19
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.

1 participant