API reference
POST /onboarding/token-mint
Self-serve, proof-of-possession-bound bearer-token issuance.
Hand over an ed25519 public key; get a JWT back bound to it
via an RFC 7800 cnf.jkt claim. Every subsequent
/a2a call carrying the token MUST also carry
X-PoP-Pubkey + X-PoP-Timestamp +
X-PoP-Signature or the request is treated as
anonymous.
curl -s https://coproduct.fly.dev/onboarding/token-mint \
-X POST -H 'content-type: application/json' \
-d '{
"caller_pubkey": "ed25519:<base64url of 32 raw pubkey bytes>",
"nonce": "<8-64 char correlator>"
}'
scope — array of capability strings bound
into the token. Server policy may narrow; currently passes
through. Defaults to [].ttl_ms — requested lifetime in milliseconds.
Clamped server-side to 3 600 000 ms (1 hour).
Defaults to 900 000 ms (15 minutes).{
"access_token": "<base64url header>.<base64url payload>.<base64url signature>",
"expires_in": 900,
"token_type": "PoP-Bound",
"cnf_thumbprint": "<base64url sha256(pubkey)>",
"principal": "a2a-caller:<cnf_thumbprint>",
"nonce": "<your input echoed back>"
}
The access_token is a compact-serialization
JWT signed by the server's ed25519 key (the same one
advertised in the Agent Card as
provenance_signer_pubkey). Its payload carries
cnf.jkt = sha256(your pubkey), which binds it
to your key.
/a2a
Each call needs all three PoP headers. The canonical
payload you sign is
METHOD|PATH|TIMESTAMP, upper-cased method,
no query string, integer seconds. ±60 seconds skew is
allowed.
TS=$(date +%s)
CANONICAL="POST|/a2a|$TS"
SIG=$(printf '%s' "$CANONICAL" | <ed25519-sign with your private key>)
curl -s https://coproduct.fly.dev/a2a \
-X POST \
-H 'content-type: application/json' \
-H "authorization: Bearer $ACCESS_TOKEN" \
-H "x-pop-pubkey: ed25519:<your b64url pubkey>" \
-H "x-pop-timestamp: $TS" \
-H "x-pop-signature: ed25519:<your b64url signature>" \
-d '{"jsonrpc":"2.0","id":"1","method":"message/send",
"params":{"message":"hello"}}'
a2a-caller:<your-thumbprint>,
a stable identity with no external IdP.400 caller_pubkey must start with
ed25519: — the key header is required.400 caller_pubkey must be 32 bytes
— raw ed25519 pubkey bytes, base64url-encoded.400 nonce must be 8-64 chars.503 server not configured with a
signing keypair — server is running unsigned (dev
mode). Won't happen on production
coproduct.fly.dev.
The coproduct-a2a Python SDK
(source)
implements this flow in
Client.send_message(...) — it handles the PoP
header construction transparently once you've set the
private key. The reference integration at
coproduct-demo-a2a-langgraph
does the full mint + verify round-trip in a LangGraph
state machine.