What is inside
A cosignature is a compact JWS over the agent, budget, action, resource, amount and currency, expiring in minutes: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.
Verify it offline
The public keys are published as a standard JWKS: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:authorization, and you can send the token as an Authorization: Bearer <jwt>
header instead. The endpoint is unauthenticated either way.
200 OK
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 nocosignature 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.