Cotal
DocsAPI Reference
Build integrationsEmbedding reference

Service endpoint access

Authority routes

With serviceEndpoints, both faces also serve /service-endpoint-authority, and without it that route answers 404. GET returns the receiver's account and space, the exact iss its service access bearers carry, and the public Ed25519 keys GET /jwks publishes. POST takes a {request, proof} envelope of at most 2 MiB. An IdP proof derives the owner, who needs a current interactive cli or web row; register needs one, and so may activate. A service's renewal proof authorizes activate, renew and status, and its access bearer authorizes ready and status; neither is owner authority. A typical sequence: the service registers and gets approval-required; you bind its name with an operator name-bind; the owner approves with decide; the service replays the same register and gets registered with its witness and snapshot; it activates and gets a credential with an access bearer of at most 60 s and a renewal proof of at most 24 h, neither outliving the native root or approval; it connects to the broker with the bearer through the composed native service callout, binds its responder, calls ready, and renews before the bearer expires. The auth provider's serviceEndpointInfo and serviceEndpointAuthority hooks are the client, and accept a service proof as well as the signed-in login.

Verified serving grant

The credential answer carries no store access and no serve grant, so a service turns it into the process-local grant serveEndpoint consumes with authorizeGuestServeGrant({ accountPublicKey, result, registration, authenticity }): the parsed result, the service's own embedded closure, and the pinned issuer's iss and published keys. The issuer and keys come from the registered space's authority URL the service already pins, tenant path included: the auth provider's serviceEndpointInfo answer carries them, and a service passes serveAuthenticityOf(info) as authenticity; an authority that does not publish them yet makes that call refuse, so the service does not serve on a bearer it cannot verify. It verifies the access bearer's EdDSA signature under the published key its kid names and the same closed claims the host's token validator checks, so a bearer the issuer did not sign for this witness brands nothing; the closure is bound to the service's own embedded contract, not to the bearer. It refuses another account, a bearer from another issuer, space, subject or key, one that names another witness or no serve duty, a closure set or artifact that is not the embedded one, and a closure that does not read at its digest. authorizeTrustedServeSnapshot stays the host's.

Renewal source

For a serving connection, the auth provider's serviceEndpointRenewal({ store, dir, witness }) hook returns that loop as a bearer function. It is an AuthProvider method, not a package export. Pass its bearer as EndpointOptions.bearer and its sentinelCreds with it. The first pull activates on the signed-in login and every later pull renews on the stored renewal proof. If that proof has expired, as after a machine was off for more than a day, the next pull replays the same activate request on the signed-in login, which mints again while the root and the approval stand. It keeps one activate and one renew requestId per witness, written to store before the first send, and replays them, with fresh material each time, for as long as the root and the approval allow. Concurrent pulls share one request. credential() is that same pull answering the validated credential result without its renewal proof, which never leaves the source, as a copy per caller; it is what authorizeGuestServeGrant takes. bearer() and credential() share one pull, so the two in flight together send one request under the same persisted ids, and an ended or closed source refuses both the same way. It owns no timer. A transport failure, rate-limited, a missing or rejected sign-in, or a store failure leaves it unavailable, and the next pull replays the same request; any other denial from the space ends it, and later pulls send nothing. state() reports expiries only, errors are fixed sentences, and close() aborts a pull in flight.

Recovery

Every effectful request is keyed by its requestId and body. Keep one requestId per operation and witness, store it with the witness and the renewal material before you send it, and reuse it with the same body after a restart: a replay is safe, a changed body under the same requestId is a conflict, and one instance accepts at most 64 distinct requests, so fresh keys per refresh or restart exhaust it. A register requestId stays bound to the body and proposal its first answer admitted: after approval, replay that exact request, and after a refusal or expiry, propose again under a new requestId. A 503 means the request may be bound: replay the same request, or read its status with target: { kind: "operation", operation, requestId }; the selector names the operation since this release, and the old { kind: "operation", requestId } no longer parses. An owner reads status only while it holds an interactive row, and a service proof reads only its own instance. registered and ready in a status mean only that the request's registration is current. A pre-activation re-registration sends expected: { state: "registered", witness } and yields a new witness; a register that another request overtakes before its barrier freezes answers conflict, so read the instance and expect its current witness. A register whose gate moved under it without a new registration, such as a freeze another barrier then aborted, also answers conflict with the witness unchanged: if the instance still shows the witness you sent, replay the same request. A register held inside the native barrier, or while another invocation of the same request is still running, answers 503; replay the same request. Once a service has activated, its registration can no longer change. Commands must be ephemeral and declare no governed trait.

On this page