KYAPay audit: six drafts to answer who is behind the agent
An agent knocks on your API. Two questions decide whether you serve it: is there a real human behind this, and can it pay. A family of six Internet-Drafts, all published on the same July day, tries to answer both in a single HTTP header.
On 2026-08-16 we read the raw text of all six drafts, pulled their GitHub repositories, checked the three IANA registries they ask to write into, and compared the paper spec against what the company behind it actually serves in production. This is what we found.
The family is called KYAPay — Know Your Agent plus Pay. It is authored by Ankit Agarwal of Skyfire Systems Inc. and Michael B. Jones of Self-Issued Consulting, the co-author of JWT, JWS and JWK. That pedigree is the reason to take it seriously, and the reason to read it carefully.
Six drafts, one day, four outside employers
Every document in the family carries the date 19 July 2026 and the expiry 20 January 2027. Five of them are labelled Standards Track. The page headers say "Web Authorization Protocol" — the IETF OAuth working group's own name — but the datatracker lists them as individual submissions with no working group attached, and each carries the boilerplate that the draft "is not endorsed by the IETF and has no formal standing in the IETF standards process".
The split of labour is worth reading as a map of who is being recruited:
draft-skyfire-oauth-kyapay-token-01— the token format. Agarwal and Jones. Replaces the earlierdraft-skyfire-kyapayprofile.draft-skyfire-oauth-using-kyapay-tokens-00— how intermediaries consume it. Adds Srinivasa Thumma of Akamai.draft-skyfire-oauth-id-verification-01— theivmclaim. Adds Nash Ali of Experian.draft-skyfire-oauth-amr-values-01— ten newamrvalues.draft-skyfire-oauth-aml-methods-00— theamlclaim. Agarwal, Jones, Ali.draft-skyfire-oauth-kyapay-token-exchange-01— trading the token for an OAuth access token. Adds Abhishek Hingnikar of Okta and Jeffrey Hickman of Ory.
A credit bureau, a CDN, and two identity vendors. That is not an accident of drafting; it is the shape of the problem. Verifying that a human authorized an agent needs someone who can verify humans (Experian), someone who sees the request before the origin does (Akamai), and someone who already runs the authorization server (Okta, Ory).
The GitHub repositories match that timeline. skyfire-xyz/draft-skyfire-oauth-kyapay-token was created on 2025-11-20; the other five were created between 2026-06-12 and 2026-07-14, and all of them stop at a commit on 2026-07-19 with messages like "Results of proofreading by Mike" and "Add history entry for adding Nash and Srini as authors". Combined stars across the six repositories: one.
Three token types, one header
KYAPay defines three JWTs, distinguished by the typ header parameter, which is REQUIRED along with kid and alg (currently ES256):
kya+jwt proves identity and cannot be charged. pay+jwt authorizes money. kya-pay+jwt carries both. The separation is the good idea in the design: an agent reading a paywalled page and an agent buying a widget present different credentials, and a seller that only needs to know a human exists never receives payment material.
All three travel in one place. The Using KYAPay Tokens draft defines a request header field with an explicit grammar:
// draft-skyfire-oauth-using-kyapay-tokens-00, Section 5.1
KYAPay-Token = token-jwt *( OWS "," OWS token-jwt )
token-jwt = 1*( ALPHA / DIGIT / "-" / "_" / "." )
The reasoning is careful. Because a JWT uses only the base64url alphabet plus the period separator, it never contains a comma, so list parsing is unambiguous. A sender MUST place exactly one JWT per list member and MUST NOT rely on ordering to convey meaning; a recipient MUST determine each token's type from its typ header parameter, not from its position, and MUST ignore members it cannot parse as JWTs while continuing with the rest. The header MUST NOT be sent over a non-TLS connection, MUST NOT be logged, and MUST NOT be cached in a way that permits replay on a different request.
That is a well-written header definition. The disagreements start with what goes inside it.
What a KYA token asserts about you
Six common claims are REQUIRED — iss, sub, aud, iat, jti, exp — plus five optional ones: tdm (target domain), ori (originator URL), env (production or sandbox), tsi (target service ID) and itg (an opaque initiator tag). iss is a URL, used to discover the JWK Set through the /.well-known/jwks.json suffix mechanism.
On top of that, the identity half adds three structured claims. hid is the human principal, REQUIRED for human identity use cases. aid is the agent, REQUIRED. apd is the agent platform, OPTIONAL. Inside them, two sub-claims are mandatory and they are the ones worth arguing about:
// draft-skyfire-oauth-kyapay-token-01, Figure 1 (abridged)
{
"iss": "https://example.com/issuer",
"iat": 1742245254,
"exp": 1773867654,
"aud": "7434230d-0861-46f2-9c2c-a6ee33d07f17",
"env": "production",
"hid": {
"email": "[email protected]" // REQUIRED
},
"aid": {
"name": "Acme Agent Extraordinaire", // REQUIRED
"creation_ip": "54.86.50.139", // REQUIRED
"source_ips": ["54.86.50.139-54.86.50.141", "1.1.1.0/24"]
}
}
Inside hid, email is REQUIRED; given name, family name, phone number, organization name, and a verifier / verified / verification_id triple are optional. Inside aid, the agent's name and its creation_ip are REQUIRED, with source_ips optionally listing ranges, CIDR blocks or domain names the agent will call from.
So the minimum viable identity assertion is: a human's email address plus the public IP of the machine that minted the token. Sent to every seller the agent touches.
This is the exact gap that SD-JWT was built for, and it is why we spent a whole post on selective disclosure in delegation chains. A KYAPay token is a plain JWT: the seller who needs to know "a verified adult human authorized this" receives the email address too, because there is no way to send one without the other.
The PAY half puts card numbers in a JWT
The payment claims are tpr (target service price), tps (pricing scheme: pay_per_use, subscription, pay_per_mb or custom), amt (amount in currency units), cur (ISO 4217 code), val (amount in the settlement network's units), mnr (maximum number of requests), stp (settlement type: coin or card) and sti (settlement metadata).
The amt / val pair is the familiar one: "amt": "15", "cur": "USD", "val": "15000000" — fifteen dollars, fifteen USDC at six decimals. Anyone who has written an x402 payload will recognize the shape.
What follows is less familiar. In the spec's own KYA-PAY example, sti contains a sixteen-digit paymentToken, an expiration month, an expiration year and a three-digit security code, alongside "type": "visa_vic". A virtual card number with its CVV, inside a bearer credential, in an HTTP request header.
Two things about that block. First, the normative text in Section 3.3.1 names those sub-claims payment_token, token_expiration_month, token_expiration_year and token_security_code in snake_case, while every example writes them in camelCase. Second, Section 3.3 declares sti REQUIRED, and Section 3.3.1 opens with "The sti claim is optional. If present, it MAY contain the following sub-claims, all of which are OPTIONAL" — and then marks type as REQUIRED. Three contradictory requirement levels in eight lines.
The third gap is structural rather than editorial. When stp is coin, the only defined type value is "usdc". There is no chain identifier, no CAIP-2 network field, no contract address. The token says fifteen USDC and does not say on which of the fifteen-plus networks USDC exists. Settlement without a network identifier is not a settlement instruction, it is a hint.
A claim vocabulary for compliance
The three satellite drafts are the part that could outlive the rest, because they are generic. None of them mention agents in their claim definitions.
ivm, from the identity verification draft, is a JSON array of strings declaring how a person was verified. Eight values: dbv (database verification of PII against an unspecified number of consumer reporting sources), dbv1 (one source), dbvm (multiple sources), dig (digital ID document, such as a mobile driver's licence), phy (physical ID document), sec (secondary documents — bank statements, utility bills), inp (in person) and vid (live video interview).
aml, from the AML draft, is likewise an array: ofac (OFAC compliance), sanc (sanctions screening), watch (watchlist screening), pep (politically exposed persons) and adv (adverse media screening).
The amr draft adds ten values to the registry RFC 8176 established: app (authenticator app), bg (background or silent network authentication), email, call, code, url, push, facliv (facial recognition with liveness check), sqa (security question answers) and psk (passkey).
Put together, a seller can read from one token that the human behind the agent was verified by a live video interview, authenticated with a passkey, and cleared sanctions and PEP screening. That is a genuinely useful vocabulary. Whether a seller should believe it is a separate question, and the drafts are honest that it is unresolved.
The empirical part: nothing is registered
Specifications that define claims live or die by IANA. We pulled the registries on 2026-08-16.
The JSON Web Token Claims registry currently holds 172 entries. The KYAPay family requests sixteen of them — tdm, tsi, ori, env, itg, hid, apd, aid, tpr, tps, amt, cur, val, mnr, stp, sti — plus ivm and aml from the satellite drafts. Number of those eighteen present in the registry: zero.
The Authentication Method Reference Values registry holds 21 values: face, fpt, geo, hwk, iris, kba, mca, mfa, otp, pin, pop, pwd, rba, retina, sc, sms, swk, tel, user, vbm, wia. Number of the ten proposed values present: zero.
The application media types registry contains no kya+jwt, no pay+jwt and no kya-pay+jwt. Two new registries the drafts propose to create — Identity Verification Methods and Anti-Money Laundering Methods — do not exist yet.
This is not a scandal; it is what "I-D Exists" means. Registration follows publication, and none of these documents has been adopted by a working group, let alone published as an RFC. But it does set the expectation correctly. Anyone integrating today is integrating against a vendor format that happens to have an IETF-shaped filename. The paperwork is a statement of intent, not of status.
What production actually does
The interesting contrast is between the drafts and the running system. Skyfire's public documentation is specific, and it does not match the paper in the places that matter.
Token lifetime is the clearest case. The token draft's examples carry iat 1742245254 and exp 1773867654 — 2025-03-17 to 2026-03-18, a 366-day credential. Section 8.2 of the companion draft says the opposite: verifiers SHOULD require short lifetimes, "for high-assurance actions, on the order of a few minutes", and MUST reject tokens exceeding local policy. Production sides with the companion draft and adds a distinction the specs do not make. Per the create-token reference, the default and maximum lifetime is 24 hours when the token targets a registered sellerServiceId, and 5 minutes when it targets a raw sellerDomainOrUrl. Minimum, 10 seconds.
That asymmetry is the correct instinct. A token minted for a counterparty you have never met should be a short-lived, single-purpose credential; a token for a service you have a relationship with can live a working day. It is the same walk-up-versus-registered split we drew in the Bearer versus x402 decision tree, arrived at from the other direction.
The federation story is also aspirational for now. The drafts describe an open ecosystem of issuers; production has one. The documented production issuer is https://app.skyfire.xyz, its JWKS lives at /.well-known/jwks.json, and when we fetched it there was exactly one key in the set — kid BEDD-0, an EC P-256 key with alg ES256. Tokens are minted by POST /api/v1/tokens against Skyfire's API with a Skyfire API key. One issuer, one signing key, one control plane.
And then the status codes, which tell you what the protocol thinks a payment challenge is:
// Skyfire, "Handling Missing or Invalid Tokens"
Missing token -> 403 Forbidden
Invalid token -> 401 Unauthorized
Insufficient balance -> 402 Payment Required
An agent that arrives without a token gets a 403 whose body is an English sentence telling it to go create an account. There is no machine-readable challenge, no advertised price, no accepted-schemes list. 402 is reserved for the narrow case of a token that exists but is underfunded. That is the inverse of x402, where 402 is the discovery mechanism and the response body carries structured payment requirements that a client can satisfy without a human ever visiting a dashboard.
Bearer, with an escape hatch
The most honest document in the family is the security section of Using KYAPay Tokens, and it deserves quoting rather than paraphrasing.
On replay: "As deployed today, KYAPay accepts this bounded in-window risk and mitigates rather than eliminates it." Verifiers MUST validate aud so a token minted for one target cannot be presented to another, MUST keep lifetimes short, and SHOULD use jti to detect replay to the same recipient.
On the malicious recipient: "A Target that legitimately receives a bearer token can, within the token's window, reuse it to act elsewhere on the agent's behalf." Audience binding limits the blast radius; only proof of possession closes it.
The escape hatch is cnf. When a token carries a confirmation key per RFC 7800, the verifier MUST additionally check a per-request signature over that key, using HTTP Message Signatures — RFC 9421, the same primitive we walked through in the Web Bot Auth post. The draft explicitly prefers signing the request itself over a detached DPoP-style proof, because it keeps a single signature covering both the request and the key binding, and lets an intermediary cache the key and then do only a fast per-request verification.
That is the right architecture. It is also optional, and production does not appear to require it.
Section 8.6 then says the quiet part out loud: "A token is only as trustworthy as its issuer." Establishing that trust at scale is "an open problem analogous to the Certificate Authority model (audits, a maintained issuer list, a removal mechanism, and possibly transparency logs); this document does not define such a framework, and deployments should not assume one exists." Until it does, trust is configured out of band.
Which is where Experian Agent Trust, announced 2026-04-30, comes in: a Human-to-Agent Binding that links a verified consumer, their device and the agent acting for them, plus an Agent Registry that scores agents over time, developed with Visa, Cloudflare and Skyfire named as ecosystem partners. The bureau is volunteering to be the CA. We covered the merchant-edge half of that same alliance in the Visa Trusted Agent Protocol deep dive.
The word that never appears
We grepped both principal drafts for x402. Zero occurrences, in either document.
They do reference MCP and A2A repeatedly — agent-to-tool and agent-to-agent are named as first-class transports for the token, including a stdio MCP channel. The token exchange draft's worked example targets resource=https://mcp.acme.example/, trading a KYAPay token for a scoped OAuth access token via the RFC 7523 JWT bearer grant. Notably it declines RFC 8693 token exchange, on the stated grounds that the KYAPay token already carries everything a separate subject_token and actor_token would.
So the identity layer is being designed against the agent protocols and against card rails, with stablecoins present as a settlement type string and absent as an architecture. Meanwhile x402 is being designed as payment execution with identity reduced to a wallet address. Two halves of the same problem, built by different people, not yet joined.
What it means for LLM4Agents
We run an OpenAI-compatible gateway that agents pay per request in stablecoins. KYAPay lands on us in three distinct ways, and they pull in different directions.
Identity is becoming a separate header from payment
Today an x402 payment payload proves a wallet controls funds and nothing else. KYAPay says the seller also wants to know which human is behind the wallet, how they were verified, and whether they cleared sanctions screening. For a model gateway that is not hypothetical: export controls, sanctions lists and per-jurisdiction model availability are real constraints on who may call which model.
The design lesson is the separation, not the vendor. Identity assertions belong in their own header with their own lifetime and their own trust anchors. Payment authorization belongs in the payment header. Anything that fuses them forces every seller who only needs one to accept both.
A gateway is exactly the intermediary these drafts describe
Using KYAPay Tokens is written for bot managers, fraud systems, account-takeover defences and CIAM platforms — components that sit in front of an origin and decide whether to pass a request. A model gateway is structurally the same position: we terminate the agent's request, decide whether to serve it, and forward to an upstream provider.
That means the consumption side of this spec is directly implementable for us: parse KYAPay-Token, validate against a configured issuer allow-list, verify aud and env, enforce a lifetime ceiling of our own regardless of what the token claims, and treat a validated hid as an attribute on the request — never as an authorization decision by itself.
The PII default is a liability we should not inherit
A token whose minimum content is an email address and a source IP, with no selective disclosure, arriving on every request, is a data-protection problem in the EU before it is an engineering problem. Our position is that a gateway should hold as little principal data as possible.
Concretely: if we accept these tokens we should validate them, extract the boolean facts we need (verified, jurisdiction, screening passed), and drop the raw claims rather than persist them. The header explicitly MUST NOT be logged. That instruction should survive contact with our observability stack.
Staying on the frontier
Four steps, in the order we think they should be taken.
First, accept identity as an optional input, not a requirement. Build a verifier that reads KYAPay-Token if present, validates it against an explicit issuer allow-list with a hard local lifetime cap, and attaches the result to the request context. An agent that presents nothing keeps working exactly as today, paying with x402. An agent that presents a validated token becomes eligible for things a wallet address alone cannot unlock: higher rate limits, restricted models, invoiced billing. Identity buys capability; it does not gate access.
Second, implement proof of possession before anyone asks. If we accept a bearer credential carrying a human's identity, the in-window replay risk is ours to explain. Requiring cnf plus an RFC 9421 request signature for any token that unlocks elevated capability costs one signature verification per request and removes the entire class of malicious-recipient reuse. We already need RFC 9421 handling for Web Bot Auth; this is the same code path with a different key source.
Third, publish the mapping between identity assurance and what it unlocks. The ivm, amr and aml vocabularies are useful precisely because they are machine-readable. A public table saying which combinations qualify an agent for which tier turns a vague trust conversation into a spec an agent developer can code against. That table is also the artifact a compliance reviewer asks for, and it costs nothing to write before the tokens exist.
Fourth, push for the join between identity and settlement. The gap is concrete and small: a KYAPay sti block with stp: "coin" has no network identifier, and an x402 payment payload has no principal. A profile that carries a CAIP-2 network in the settlement block, and an x402 extension that carries an identity token reference, would let one request answer both questions without either spec swallowing the other. We track the x402 extensions layer for exactly this kind of seam — see the extensions audit — and this is the seam worth filing against.
The underlying bet in KYAPay is that sellers will not serve autonomous agents until they know who is accountable. That bet looks correct. Whether the answer is a bearer JWT with a credit bureau underwriting the issuer, or something with selective disclosure and on-chain attestation, is still open. What is not open is that a payment alone will stop being a sufficient introduction.
Pay per request, no dashboard required
An OpenAI-compatible gateway where an agent settles in stablecoins and identity is a capability, not a gate.
Register an agent