Download OpenAPI specification:
The public gateway of OktoID, a national-scale self-sovereign digital identity platform. Its routes accept the staff population (x-oktoid-population). Generated from the contracts by apps/gateway/scripts/openapi.ts; never edited by hand. Health routes are internal and are not part of this API.
Requires Idempotency-Key. kind may be omitted from the body; the gateway then reads it as organization, the only kind slice 0 creates. The new subject path is parentPath (default: the caller scope path) plus slug; a parent outside the caller scope is refused.
Two rules on displayName beyond its schema, both because an organization name is published in the trust tree once the organization is admitted. Its letters must come from the scripts this deployment allows (Latin and Arabic unless the operator configured otherwise), and no unbroken run of letters may mix two scripts (a space, hyphen, slash, digit or any punctuation ends a run, so a Latin word joined to an Arabic word by a hyphen is accepted); digits, punctuation and combining marks are always allowed. A name that fails is refused with 400 and code subject.name_script. And it must not read the same as the name of an organization above the new one or beside it, once capitals, character width, invisible characters and spacing are set aside: 409 with code subject.name_taken, whose detail names the path of that organization. Only organizations inside the caller scope are compared, so a 201 is not a promise: the same comparison is made across the whole trust tree when the organization is admitted.
| Idempotency-Key required | string^[\x21-\x7e]{8,128}$ 8 to 128 printable ASCII characters, unique per intended change and reused on a retry of it. |
| Oktoid-Acting-For | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... The organization (a subject id) the caller acts for. A human caller sends it; a caller with no grant for that organization is refused in the same shape as one with no grant at all, which does not say whether the organization exists. A machine client sends none. |
| kind required | string Value: "organization" |
| slug required | string^(?!..--) |
| displayName required | string [ 1 .. 200 ] characters ^(?![\u061c\u200e\u200f])(?=.*[^\p{White_Spac... |
| parentPath | string <= 1024 characters ^(/[a-z0-9][a-z0-9-]{0,62}){1,16}$ |
{- "kind": "organization",
- "slug": "acme",
- "displayName": "Acme Licensing Office",
- "parentPath": "/authority"
}{- "id": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
- "kind": "organization",
- "displayName": "Acme Licensing Office",
- "path": "/authority/acme",
- "status": "active",
- "createdAt": "2026-03-03T09:41:07.552Z"
}A subject that does not exist and one outside the caller scope get the same 404.
| id required | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... |
| Oktoid-Acting-For | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... The organization (a subject id) the caller acts for. A human caller sends it; a caller with no grant for that organization is refused in the same shape as one with no grant at all, which does not say whether the organization exists. A machine client sends none. |
{- "id": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
- "kind": "organization",
- "displayName": "Acme Licensing Office",
- "path": "/authority/acme",
- "status": "active",
- "createdAt": "2026-03-03T09:41:07.552Z"
}Requires Idempotency-Key. The person is a leaf below parentPath (default: the caller scope path); a parent outside the caller scope, or a parent that is itself a person, is refused. givenName, familyName and email are personal data: stored encrypted under the person's own key, never logged, and absent from the reply once the person is erased. An address another person already has (compared without regard to case) is refused with 409 and code person.email_taken. invitation says where the invitation stands.
| Idempotency-Key required | string^[\x21-\x7e]{8,128}$ 8 to 128 printable ASCII characters, unique per intended change and reused on a retry of it. |
| Oktoid-Acting-For | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... The organization (a subject id) the caller acts for. A human caller sends it; a caller with no grant for that organization is refused in the same shape as one with no grant at all, which does not say whether the organization exists. A machine client sends none. |
| givenName required | string [ 1 .. 200 ] characters ^(?![\u061c\u200e\u200f])(?=.*[^\p{White_Spac... |
| familyName required | string [ 1 .. 200 ] characters ^(?![\u061c\u200e\u200f])(?=.*[^\p{White_Spac... |
| email required | string <email> [ 3 .. 254 ] characters |
| parentPath | string <= 1024 characters ^(/[a-z0-9][a-z0-9-]{0,62}){1,16}$ |
| invite | boolean |
{- "givenName": "Layla",
- "familyName": "Haddad",
- "email": "layla.haddad@acme.example.org",
- "parentPath": "/authority/acme",
- "invite": true
}{- "id": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "kind": "person",
- "path": "/authority/acme/0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "status": "active",
- "createdAt": "2026-03-03T09:44:30.118Z",
- "erased": false,
- "givenName": "Layla",
- "familyName": "Haddad",
- "email": "layla.haddad@acme.example.org",
- "invitation": {
- "state": "pending"
}
}A person that does not exist and one outside the caller scope get the same 404. After erasure the reply has erased: true and no personal field.
| id required | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... |
| Oktoid-Acting-For | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... The organization (a subject id) the caller acts for. A human caller sends it; a caller with no grant for that organization is refused in the same shape as one with no grant at all, which does not say whether the organization exists. A machine client sends none. |
{- "id": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "kind": "person",
- "path": "/authority/acme/0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "status": "active",
- "createdAt": "2026-03-03T09:44:30.118Z",
- "erased": false,
- "givenName": "Layla",
- "familyName": "Haddad",
- "email": "layla.haddad@acme.example.org",
- "invitation": {
- "state": "sent"
}
}Requires Idempotency-Key; takes no body. Sends a fresh invitation to the address on file, for an invitation that expired, failed or was never asked for. An invitation still in progress is answered as it stands and nothing new is sent. Takes the person.create permission. An erased person, and one with no address on file (the root account), are refused with 409 state.
| id required | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... |
| Idempotency-Key required | string^[\x21-\x7e]{8,128}$ 8 to 128 printable ASCII characters, unique per intended change and reused on a retry of it. |
| Oktoid-Acting-For | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... The organization (a subject id) the caller acts for. A human caller sends it; a caller with no grant for that organization is refused in the same shape as one with no grant at all, which does not say whether the organization exists. A machine client sends none. |
{- "personId": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "invitation": {
- "state": "pending"
}
}Requires Idempotency-Key. Delegation narrows: the bundle's permissions must be a subset of the caller's own, for an organization inside the caller's own subtree; anything else is refused in the standard shape. Revoking a grant takes effect within one token lifetime: the gateway remembers a resolved principal for at most five minutes (principalCacheSeconds, never past the token's own expiry), and a revoked grant authorises until that entry expires.
| Idempotency-Key required | string^[\x21-\x7e]{8,128}$ 8 to 128 printable ASCII characters, unique per intended change and reused on a retry of it. |
| Oktoid-Acting-For | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... The organization (a subject id) the caller acts for. A human caller sends it; a caller with no grant for that organization is refused in the same shape as one with no grant at all, which does not say whether the organization exists. A machine client sends none. |
| granteeId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... |
| organizationId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... |
| bundle required | string^[A-Z][A-Z0-9_]{0,63}$ |
{- "granteeId": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
- "bundle": "ENTITY_ADMIN"
}{- "id": "0199c1f4-7c20-7e5b-8d16-93a0b4c5e2f1",
- "granteeId": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
- "bundle": "ENTITY_ADMIN",
- "grantedBy": "0199c1d0-5b7e-7a21-9c43-2f8e1d6a0b57",
- "status": "active",
- "createdAt": "2026-03-03T09:46:02.871Z"
}Filtered by organization, grantee and status, newest first, in pages: pass nextCursor back as cursor. Revoking a grant takes effect within one token lifetime: the gateway remembers a resolved principal for at most five minutes (principalCacheSeconds, never past the token's own expiry), and a revoked grant authorises until that entry expires.
| organization | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... Only grants for this organization. |
| grantee | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... Only grants held by this grantee. |
| status | any Enum: "active" "revoked" |
| limit | integer [ 1 .. 200 ] |
| cursor | string [ 1 .. 256 ] characters |
| Oktoid-Acting-For | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... The organization (a subject id) the caller acts for. A human caller sends it; a caller with no grant for that organization is refused in the same shape as one with no grant at all, which does not say whether the organization exists. A machine client sends none. |
{- "grants": [
- {
- "id": "0199c1f4-7c20-7e5b-8d16-93a0b4c5e2f1",
- "granteeId": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
- "bundle": "ENTITY_ADMIN",
- "grantedBy": "0199c1d0-5b7e-7a21-9c43-2f8e1d6a0b57",
- "status": "active",
- "createdAt": "2026-03-03T09:46:02.871Z"
}
], - "nextCursor": "page-2.0199c1f4-7c20"
}Requires Idempotency-Key; takes no body. Revoking an already revoked grant answers the same revoked grant. Revoking a grant takes effect within one token lifetime: the gateway remembers a resolved principal for at most five minutes (principalCacheSeconds, never past the token's own expiry), and a revoked grant authorises until that entry expires.
| id required | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... |
| Idempotency-Key required | string^[\x21-\x7e]{8,128}$ 8 to 128 printable ASCII characters, unique per intended change and reused on a retry of it. |
| Oktoid-Acting-For | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... The organization (a subject id) the caller acts for. A human caller sends it; a caller with no grant for that organization is refused in the same shape as one with no grant at all, which does not say whether the organization exists. A machine client sends none. |
{- "id": "0199c1f4-7c20-7e5b-8d16-93a0b4c5e2f1",
- "granteeId": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
- "bundle": "ENTITY_ADMIN",
- "grantedBy": "0199c1d0-5b7e-7a21-9c43-2f8e1d6a0b57",
- "status": "revoked",
- "createdAt": "2026-03-03T09:46:02.871Z",
- "revokedAt": "2026-04-14T15:20:44.309Z"
}Answers the caller's own active grants: for each, the organization id (the value to send as Oktoid-Acting-For on every other call), its path and the bundle code. It acts for no organization, so it takes no Oktoid-Acting-For (a header sent is ignored) and needs no permission. A caller with no grant, a token that maps to no principal and a suspended principal all get 200 with an empty list. Read fresh on every call, not through the principal cache, so a grant made or revoked shows at once.
{- "grants": [
- {
- "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
- "path": "/authority/acme",
- "bundle": "ENTITY_ADMIN"
}
]
}Requires Idempotency-Key. Names an organization inside the caller scope; the issuance module reads the organization itself and takes nothing else about it from the body. An organization may hold one identifier that has not failed: a second is refused with 409 and code identifier.exists.
Two methods exist. webvh, the default, publishes a signed, append-only log: every change is an entry chained to the one before, so a verifier can tell a key rotation from a substitution, and the next update key is committed in advance, so a lost key does not freeze the identifier. web publishes one replaceable document with no history and no recovery, and whoever controls the domain can replace it; choose it only for verifiers that resolve nothing else. A webvh identifier is also served in did:web form beside its log, so it rarely needs choosing. The method is fixed at creation, and a deployment may not permit web, in which case asking for it is refused with 400 and code identifier.method_not_permitted, at body.method.
The answer is 202 and the identifier in state placing: its keys are generated and its document rendered just after this reply, once the organization has been placed on an agent. Read the identifier at the Location given until it is prepared, then preview it and publish it. Nothing is published by creating.
host serves the identifier on a delegated host instead of the organization's own publisher host. Its DNS must already point at the platform: that is checked at every publication, which is refused with 409 and code identifier.host_unverified while the name resolves elsewhere; correct the DNS record and publish again. A name that is an address, or that sits under the platform's own domains anywhere but beneath the organization's own, is refused with 400 and code identifier.host_refused, at body.host. Without host, the organization's own publisher host is used, and when another identifier already serves that host the answer is 409 with code identifier.host_taken; the answer never says whose.
| Idempotency-Key required | string^[\x21-\x7e]{8,128}$ 8 to 128 printable ASCII characters, unique per intended change and reused on a retry of it. |
| Oktoid-Acting-For | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... The organization (a subject id) the caller acts for. A human caller sends it; a caller with no grant for that organization is refused in the same shape as one with no grant at all, which does not say whether the organization exists. A machine client sends none. |
| organizationId required | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... The organization to create the identifier for: an active organization inside the scope the caller acts for. It may hold one identifier that has not failed. |
"webvh" (string) or "web" (string) Default: "webvh" The DID method. Leave it out for | |
| host | string <= 253 characters ^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-... A delegated host to serve the identifier on, such as |
{- "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e"
}{- "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
- "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
- "path": "/authority/acme",
- "method": "webvh",
- "history": true,
- "host": "acme.identity.example.org",
- "status": "placing",
- "version": 0,
- "versionLimit": 24,
- "createdAt": "2026-03-03T10:02:11.004Z",
- "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914"
}Filtered by organization and status, in pages: pass nextCursor back as cursor. A cursor this list did not issue is refused with 400 and code identifier.cursor_invalid, at query.cursor. An identifier outside the caller scope is never listed.
| organization | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... Only the identifier of this organization. |
| status | any Enum: "placing" "prepared" "published" "failed" Only identifiers in this state. |
| limit | integer [ 1 .. 200 ] The most entries to return in one page, at most 200. |
| cursor | string [ 1 .. 256 ] characters The |
| Oktoid-Acting-For | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... The organization (a subject id) the caller acts for. A human caller sends it; a caller with no grant for that organization is refused in the same shape as one with no grant at all, which does not say whether the organization exists. A machine client sends none. |
{- "identifiers": [
- {
- "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
- "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
- "path": "/authority/acme",
- "method": "webvh",
- "history": true,
- "host": "acme.identity.example.org",
- "status": "published",
- "version": 1,
- "versionLimit": 24,
- "createdAt": "2026-03-03T10:02:11.004Z",
- "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
- "updateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
- "renderedAt": "2026-03-03T10:02:12.870Z",
- "previewedAt": "2026-03-03T10:05:40.215Z",
- "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505",
- "did": "did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org",
- "scid": "QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
- "versionId": "1-QmQq6Kg4ZZ1p49znzxnWmes4LkkWgMWLrnrfPre8UD56bz",
- "retiredUpdateKeyIds": [
- "uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI"
], - "publishedAt": "2026-03-03T10:06:02.593Z"
}
]
}An identifier that does not exist and one outside the caller scope get the same 404. This is the route to poll after creating: status moves from placing to prepared within seconds. method and history say which kind of identifier it is: history is false for did:web, whose changes cannot be verified against what came before.
| id required | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... |
| Oktoid-Acting-For | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... The organization (a subject id) the caller acts for. A human caller sends it; a caller with no grant for that organization is refused in the same shape as one with no grant at all, which does not say whether the organization exists. A machine client sends none. |
{- "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
- "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
- "path": "/authority/acme",
- "method": "webvh",
- "history": true,
- "host": "acme.identity.example.org",
- "status": "prepared",
- "version": 0,
- "versionLimit": 24,
- "createdAt": "2026-03-03T10:02:11.004Z",
- "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
- "updateKeyId": "uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI",
- "nextUpdateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
- "renderedAt": "2026-03-03T10:02:12.870Z",
- "previewedAt": "2026-03-03T10:05:40.215Z",
- "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505"
}The document as it will be published, the URLs it will be served at and the keys behind it. Reading it is what allows publication: publish is refused until the current document has been previewed, and a document rendered again since the last preview needs a new one. document is canonical JSON text; parse it to read it, and compare documentDigest with the SHA-256 of its bytes to know nothing changed it on the way. For a webvh identifier, parallelDocument is the did:web form served beside the log. An identifier still in placing has no document yet and is refused with 409; read the identifier until it is prepared.
| id required | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... |
| Oktoid-Acting-For | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... The organization (a subject id) the caller acts for. A human caller sends it; a caller with no grant for that organization is refused in the same shape as one with no grant at all, which does not say whether the organization exists. A machine client sends none. |
{- "identifier": {
- "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
- "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
- "path": "/authority/acme",
- "method": "webvh",
- "history": true,
- "host": "acme.identity.example.org",
- "status": "prepared",
- "version": 0,
- "versionLimit": 24,
- "createdAt": "2026-03-03T10:02:11.004Z",
- "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
- "updateKeyId": "uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI",
- "nextUpdateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
- "renderedAt": "2026-03-03T10:02:12.870Z",
- "previewedAt": "2026-03-03T10:05:40.215Z",
- "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505"
}, - "version": 1,
- "document": "{\"@context\":[\"https://www.w3.org/ns/did/v1\",\"https://w3id.org/security/multikey/v1\"],\"alsoKnownAs\":[\"https://acme.identity.example.org\",\"did:web:acme.identity.example.org\"],\"assertionMethod\":[\"did:webvh:{SCID}:acme.identity.example.org#key-1\"],\"authentication\":[\"did:webvh:{SCID}:acme.identity.example.org#key-1\"],\"id\":\"did:webvh:{SCID}:acme.identity.example.org\",\"verificationMethod\":[{\"controller\":\"did:webvh:{SCID}:acme.identity.example.org\",\"id\":\"did:webvh:{SCID}:acme.identity.example.org#key-1\",\"publicKeyMultibase\":\"zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169\",\"type\":\"Multikey\"}]}",
- "documentDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505",
- "parallelDocument": "{\"@context\":[\"https://www.w3.org/ns/did/v1\",\"https://w3id.org/security/multikey/v1\"],\"alsoKnownAs\":[\"https://acme.identity.example.org\",\"did:webvh:{SCID}:acme.identity.example.org\"],\"assertionMethod\":[\"did:web:acme.identity.example.org#key-1\"],\"authentication\":[\"did:web:acme.identity.example.org#key-1\"],\"id\":\"did:web:acme.identity.example.org\",\"verificationMethod\":[{\"controller\":\"did:web:acme.identity.example.org\",\"id\":\"did:web:acme.identity.example.org#key-1\",\"publicKeyMultibase\":\"zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169\",\"type\":\"Multikey\"}]}",
- "scid": null,
- "scidPlaceholder": "{SCID}"
}Requires Idempotency-Key; takes no body. The agent signs the previewed document with keys that never leave their token, and the result is written to the identifier host: the log at /.well-known/did.jsonl and the did:web form at /.well-known/did.json for webvh, the second alone for web. The identifier then resolves, and a resolver may go on seeing the previous version for up to five minutes.
Refused with 409 when the current document has not been previewed, when the identifier is not prepared, and when another publication of the same identifier won the race; read the identifier again and decide. What is signed is compared with what was previewed before a byte is written. If they differ nothing is published, the identifier becomes failed with failureReason: document-mismatch and its keys are destroyed, and the refusal is recorded in the audit trail.
| id required | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... |
| Idempotency-Key required | string^[\x21-\x7e]{8,128}$ 8 to 128 printable ASCII characters, unique per intended change and reused on a retry of it. |
| Oktoid-Acting-For | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... The organization (a subject id) the caller acts for. A human caller sends it; a caller with no grant for that organization is refused in the same shape as one with no grant at all, which does not say whether the organization exists. A machine client sends none. |
{- "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
- "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
- "path": "/authority/acme",
- "method": "webvh",
- "history": true,
- "host": "acme.identity.example.org",
- "status": "published",
- "version": 1,
- "versionLimit": 24,
- "createdAt": "2026-03-03T10:02:11.004Z",
- "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
- "updateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
- "renderedAt": "2026-03-03T10:02:12.870Z",
- "previewedAt": "2026-03-03T10:05:40.215Z",
- "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505",
- "did": "did:webvh:QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ:acme.identity.example.org",
- "scid": "QmfGEUAcMpzo25kF2Rhn8L5FAXysfGnkzjwdKoNPi615XQ",
- "versionId": "1-QmQq6Kg4ZZ1p49znzxnWmes4LkkWgMWLrnrfPre8UD56bz",
- "retiredUpdateKeyIds": [
- "uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI"
], - "publishedAt": "2026-03-03T10:06:02.593Z"
}Requires Idempotency-Key; takes no body. Generates a new assertion key and renders the document again with it. The identifier returns to state prepared and the version already published stays served; preview the new document, then publish it. For webvh the publication appends one log entry, signed by the current update key, that a verifier can check against the entry before. For web it overwrites the document, and nothing lets a verifier tell that from a substitution. The replaced key is kept and listed in retiredAssertionKeyIds, because a credential signed under it must stay verifiable. The host and the method cannot be changed. An identifier that is not published is refused with 409.
| id required | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... |
| Idempotency-Key required | string^[\x21-\x7e]{8,128}$ 8 to 128 printable ASCII characters, unique per intended change and reused on a retry of it. |
| Oktoid-Acting-For | string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]... The organization (a subject id) the caller acts for. A human caller sends it; a caller with no grant for that organization is refused in the same shape as one with no grant at all, which does not say whether the organization exists. A machine client sends none. |
{- "id": "0199c2a0-4e1b-7d3c-8f25-6a9b0c7d1e42",
- "organizationId": "0199c1f1-03aa-7d10-b6c2-51e4a7f09d3e",
- "path": "/authority/acme",
- "method": "webvh",
- "history": true,
- "host": "acme.identity.example.org",
- "status": "prepared",
- "version": 0,
- "versionLimit": 24,
- "createdAt": "2026-03-03T10:02:11.004Z",
- "createdBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "assertionKeyId": "dMm7JVVgAw3vNZgfqLqxnoGoLyOQgDPotMP76t_2UHw",
- "updateKeyId": "uAztFfC0taJ4RV-0lO8cfLZ60yrp4RsBuDMsmyZC6kI",
- "nextUpdateKeyId": "Qk3v9mT1xW7cHn2ZpL5sYd8RbA0uEo6FgJ4iKqVtN_s",
- "renderedAt": "2026-03-03T10:02:12.870Z",
- "previewedAt": "2026-03-03T10:05:40.215Z",
- "previewedBy": "0199c1ee-9f40-73b8-a1d7-6c2b0e58f914",
- "previewedDigest": "96f7627af971a755b1f91af028f14d87cd5679ec5ae56e847582b2249460b505"
}Unauthenticated, and answered by both surfaces. The deploy agent waits on this until the release it deployed is the one reported. version is what the image build set: 0.0.0+ and the abbreviated commit the image was built from, or dev for a build that set none.
{- "service": "gateway",
- "version": "0.0.0+7e3ad643a1f2"
}