Skip to content

feat: v1 surface parity — connections.get, authed session read, webhook endpoints, key rotation (0.2.0) - #1

Merged
BSalaeddin merged 2 commits into
mainfrom
feat/v1-surface-parity-0.2.0
Aug 19, 2026
Merged

feat: v1 surface parity — connections.get, authed session read, webhook endpoints, key rotation (0.2.0)#1
BSalaeddin merged 2 commits into
mainfrom
feat/v1-surface-parity-0.2.0

Conversation

@BSalaeddin

Copy link
Copy Markdown
Collaborator

Brings dodomain-sdk up to the API's current /v1 surface. Every callable REST
operation now has a method, sync and async. Nothing was removed and no existing
return value changed shape, so 0.1.0 → 0.2.0 is a drop-in upgrade.

Verified against three sources, not one: the route handlers on dodomain@origin/main,
packages/core/src/schemas.ts, and the live OpenAPI document at
https://dodomain.io/docs/openapi.json.

What ships

Method Route
connections.get(id) GET /v1/connections/{id}
sessions.get(session_id) GET /v1/sessions/{id} (the authed arm)
webhook_endpoints.list() GET /v1/webhook-endpoints
webhook_endpoints.create(url=…) POST /v1/webhook-endpoints
webhook_endpoints.update(id, url=…) PATCH /v1/webhook-endpoints/{id}
webhook_endpoints.delete(id) DELETE /v1/webhook-endpoints/{id}
webhook_endpoints.rotate_secret(id) POST /v1/webhook-endpoints/{id}/rotate-secret
webhook_endpoints.get(id) (no route — client-side lookup over list, see below)
keys.rotate() POST /v1/keys/rotate

Plus additive fields the API grew after 0.1.0: Connection.record_fqdns,
Session.records, Session.warnings; and PermissionError_.secret_key_required
so the secret-key-only 403 is distinguishable from a missing OAuth scope.

Three findings worth a reviewer's eye

1. webhook_endpoints.get has no route behind it. The spec has no
GET /v1/webhook-endpoints/{id} — only the collection, PATCH, DELETE and
rotate-secret. Rather than skip the method or fake a request, get reads the
app's own list and picks the id out, and says so in three places: the docstring,
a status_code == 0 on the NotFoundError it raises (no 404 came from the
server), and a test named for it. The semantics are faithful — the list contains
exactly this app's endpoints, so an id missing from it is either someone else's
or nobody's, which is precisely what the API refuses to distinguish. If the
Node SDK ships a get here, the two should agree on this;
flagging for parity.

2. The two session read arms answer different shapes, and the difference is
easy to get wrong.
sessions.get returns composed records — type/host/fqdn
with no value (zComposedRecord picks only two fields off the record
schema) — plus appId, connectionId and a server-derived expired, and no
returnUrl. That is a new IntegratorSession model, not a reuse of
PublicSession. Handing a dd_sess_ token to get would succeed server-side
and answer the public shape, then fail deep in the parser on a missing appId,
so the SDK refuses a token up front and points at retrieve.

3. Two README claims were false and are corrected here. The webhook section
still described the pre-cutover {event, data} body — the envelope has been
{id, type, occurredAt, data} + a deprecated event alias since 2026-08-06, and
a reader following the old text would have built the wrong parser. And it stated
that doDomain publishes no OpenAPI document; it does. verify_webhook itself
needed no change: it was wire-format agnostic by design, which is exactly why it
survived the cutover.

Evidence

  • ruff check · ruff format --check · ty check — clean.
  • pytest329 passed, coverage 99.20% (gate is 90%).
  • Every new method has both a sync and an async test, and the async-parity suite
    gained a structural check that the two clients expose the same resource tree,
    so a method added to one and forgotten on the other now fails immediately
    rather than waiting for someone to remember a parametrized case.
  • Additive fields are proven tolerant and strict: a payload recorded before
    records / recordFqdns existed still parses, while a field present with the
    wrong type fails loudly.
  • Live prod probe (unauthenticated, non-mutating): all four new routes answer
    401 on app.dodomain.io — a route that did not exist would answer 404.
  • The prod e2e gains the authed session read, connections.get (compared against
    the list element), the endpoint list (asserting no secret is ever on a read
    surface), and a 401 probe of the secret-key-only routes.

keys.rotate() is deliberately never called live. Rotation has no grace
window, so a real call would invalidate the very DODOMAIN_SECRET_KEY the CI
e2e job authenticates with — the response is the only copy of the replacement and
nothing in the suite could store it. Same reasoning keeps endpoint writes out
of prod. Both are fully covered against respx, and what the e2e proves instead
is that the routes are deployed and reject a bad credential.

Not published to PyPI by this PR.

@BSalaeddin
BSalaeddin merged commit 2b2e814 into main Aug 19, 2026
7 of 8 checks passed
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