Skip to main content
A decision stored in our database only binds an executor that chooses to ask us, and asks us every time. Anything already holding the means to act can act without checking, at which point a control layer is a log of advice that may have been ignored. So an allowed decision comes back signed.

What is inside

A cosignature is a compact JWS over the agent, budget, action, resource, amount and currency, expiring in minutes:
The agent carries that string to whatever executes the action. The executor verifies it, and if it does not verify, the action does not happen. That is what makes Kordio a required signer without making it a custodian: we never hold your funds and never hold your keys. The JWS is ES256 over these claims: For a payment intent, action_type is the literal payment.create and resource falls back to the payment’s counterparty.

Pinning the decision

policy_version and frame_hash are what let a receipt outlive the policy that produced it. policy_version is a content digest of the resolved policy set: the same rules always produce the same version, any edit produces a different one, and a module imported by a policy counts, so changing a shared module moves the version of every policy that imports it. Two decisions carrying the same policy_version were judged by identical rules. Store it next to the receipt and “which limits applied here” has an answer six months later, whatever the policy says by then. frame_hash covers more than the claims do. The claims echo the action type, resource and amount; the frame hash is taken over those plus the currency, agent, budget, workspace and the whole metadata object. If you sent a purchase order number in metadata, the receipt is bound to that number even though no claim repeats it. Recompute the hash from the request you hold and a match proves the receipt is for that request and no other. Both claims are null on decisions recorded before your deployment started emitting them.
Check the claims, not just the signature. A valid signature for 120toonevendorisnotauthoritytosend120 to one vendor is not authority to send 1,200 to another. A verifier that checks exp and the signature but not amount_cents and resource is not doing the job.

Verify it offline

The public keys are published as a standard JWKS:
Cache it and verify locally. No call to Kordio is needed, in any language with a JOSE library.
Offline verification is the important property. It means an authorization stays checkable when Kordio is slow, and past authorizations remain verifiable against the published keys whatever happens to us later.

Or ask us

Local verification cannot tell you whether the authorization has already been spent. A signature proves authority was granted, not that it is unused. The endpoint checks both:
The field is authorization, and you can send the token as an Authorization: Bearer <jwt> header instead. The endpoint is unauthenticated either way.
200 OK
An invalid cosignature is a 422, not a 200 with valid: false in a success envelope:
422 Unprocessable Entity
Use offline verification in the hot path and this endpoint where you need settlement state. It re-loads the intent and requires it to still be pending or requires_approval, which is the only check that can tell an unused authorization from a spent one.

Rotation

Keys rotate without invalidating authorizations already in flight. Retired keys keep verifying and stop signing, and both appear in the JWKS, so a cosignature issued a minute before a rotation still checks out. If cosigning is not configured on a deployment, the JWKS returns an empty set and decisions come back with no cosignature field rather than an unverifiable one. Treat a missing key as “not authorized to execute”, never as “authorized without a signature”.

Spending it

POST /control/v1/cosign/verify answers whether authority exists and does not change anything, so a relying party can ask twice without being punished for checking. POST /control/v1/cosign/consume spends it. The first call returns the same body as verify; every call after it fails with already_consumed, and verify reports already_consumed from then on too. Neither endpoint needs a credential, so treat consuming as the act of claiming the authority. Use consume at the moment you act on the decision, not when you receive it. A cosignature is otherwise replayable until the executor reports the action finished, and for a counterparty “valid” means pay.
consumed_by is optional and recorded against the workspace, so a dispute can name who spent it.

Next steps

What Kordio guarantees

Signature lifetime, revocation, and what a cosignature does not promise.

API reference

The verify endpoint and the JWKS document.