The user_id, the one identity
A bare lowercase UUID, minted at first sign-in, stable across all of a user's organizations, projects, and API keys. It reaches your systems in exactly two places, and they are the same string, guaranteed:
| Field | Where |
|---|---|
| sub | on the enrollment token — once, at enrollment; you bind the participant to it |
| user_id | on SERVED / FAILED verification results — continuously; you compare against the binding |
A request we cannot attribute to a person comes back with user_id omitted: your comparison fails safe, nobody is credited, nothing errors. That is the case for operator tenants, for multi-owner shared organizations, and for a self-registered agent that no human has claimed yet: its searches are served on the unclaimed tier and verify as SERVED without a user_id until the claim. Issuer https://platform.nyks.dev, algorithm EdDSA (pin it), scope verification:read.
1 · GET /.well-known/jwks.json — our public keys
GET https://platform.nyks.dev/.well-known/jwks.json
{"keys":[{"kty":"OKP","crv":"Ed25519",
"x":"JJ156dBdNd1bgzBASJtE9xJs0jxjDlhAhBMEt1bQOi0",
"kid":"747eb17c7478febe","alg":"EdDSA","use":"sig"}]}Cache per Cache-Control: max-age=300. During rotation the old and new keys are published together; fetch-on-unknown-kid is safe.
2 · The enrollment token — where user_id comes from
{"alg":"EdDSA","typ":"enroll+jwt","kid":"747eb17c7478febe"}
{
"iss": "https://platform.nyks.dev",
"sub": "0321a11e-1a31-4d98-b51f-3faf6f838d3e",
"aud": "twilight-slot-3",
"exp": 1787662213,
"iat": 1787661313,
"jti": "a6327a8c8352c0d25b1f0b02e9a448e2"
}Users generate this on the Mining page and paste it into tokendrop-proxy enroll. Validate: pin EdDSA at parse time, resolve kid via JWKS, check iss / aud / exp (15-minute lifetime), enforce single-use jti, read sub. No email, name, or profile claim will ever appear. The typ is enroll+jwt; access tokens are at+jwt, and the verification endpoint refuses anything else, so a leaked enrollment token cannot call the API below.
2a · A second issuance path: the claimed agent
Since 2026-09-09 the same token has two senders. The session path is unchanged: the Mining page mints from a signed-in browser, sub is the session's user id. The agent path is new: a headless agent that registered itself and was then claimed by a person calls, with its own bearer key and no browser,
curl -X POST https://agents-v1.nyks.dev/v1/agents/enroll \
-H "Authorization: Bearer sr-…" -H "Content-Type: application/json" \
-d '{"slot": "twilight-slot-3"}'
{"token": "eyJhbGciOiJFZERTQSIs…", "slot": "twilight-slot-3", "expires_at": "…"}sub is the claiming user, never a value the request supplies (a body naming one is rejected). The route refuses unless the agent is claimed, the key carries the mining scope granted at the claim, and the claimer resolves to a real user. Both paths call one signer with one claim-construction function, so what you receive and verify is byte-for-byte the same token shape from the same key: same issuer, typ, claim set, 15-minute life and JWKS. A note about a second sender, not a change to the message.
3 · POST /oauth/token — get an access token
curl -X POST https://platform.nyks.dev/oauth/token \
-d "grant_type=client_credentials" \
-d "client_id=minis-slot-3" \
-d "client_secret=$MINIS_CLIENT_SECRET"
{"access_token":"eyJhbGciOiJFZERTQSIs…","token_type":"Bearer",
"expires_in":3600,"scope":"verification:read"}One hour lifetime; refresh = the same exchange (HTTP Basic also accepted). A 401 anywhere means "refresh and retry", never an answer about a request. Revocation of a client takes effect on its next request, not at token expiry. The exchange is paced: 429 slow_down with Retry-After past 30/min per address — one exchange an hour is all a slot needs, so only a loop ever sees it.
4 · POST /v1/verifications — resolve request ids to user data
Batched (1–500 ids), read-only, safe to retry. The ids are the X-Request-Id values your proxy captured from POST /v1/search responses.
curl -X POST https://platform.nyks.dev/v1/verifications \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"client_batch_id":"vfy_01J8XM4Q7B",
"request_ids":["01a038ea-2356-7fd5-983c-ffa6745559d5",
"req_that_never_existed"]}'
{"client_batch_id":"vfy_01J8XM4Q7B","results":[
{"request_id":"01a038ea-2356-7fd5-983c-ffa6745559d5",
"status":"SERVED",
"user_id":"0321a11e-1a31-4d98-b51f-3faf6f838d3e",
"served_at":"2026-08-25T12:34:29.000Z",
"usage":{"cost_micros":5000},
"decision":{"tier":"fast","providers":["openai","exa"]},
"candidates":[{"provider":"openai","status":"error"},
{"provider":"exa","status":"ok"}],
"chosen":1},
{"request_id":"req_that_never_existed","status":"NOT_FOUND"}
]}Always 200 when the batch was processed: exactly one result per requested id, positionally. Statuses form a closed set; unknown values are retryable.
| Status | Kind | Meaning |
|---|---|---|
| SERVED | conclusive | existed and was served — check user_id |
| FAILED | conclusive | existed, attributed, not servable (failure_reason says why) |
| NOT_FOUND | conclusive | no such id within retention — permanently rejects the work |
| NOT_YET_DURABLE | retry | id issued < 15 min ago, not yet queryable |
| EXPIRED | retry, then age out | older than the 30-day retention window |
| UNAVAILABLE | retry | we could not answer for this id right now |
chosenis always present on SERVED and FAILED: an integer index into candidates,-1= none.decision.providersis the normative array;provider_setdoes not exist in this schema.usage.cost_microsis summed from the billing ledger at query time. Slow-lane spend is included, so a later re-verify can show more.- No content-bearing field exists: no answer text, citations, query text, URLs, or snippets.
- No HTTP status is ever a verdict.
400fix and retry ·401refresh ·429pacing (honor Retry-After) ·5xxretry. - Rate limit 60/min per client, burst 120, with
X-RateLimit-RemainingandX-RateLimit-Reseton every response. Measured p99 for a full 500-id batch on production: 196 ms.
Fetch this reference without a login
The same reference as a plain-markdown file your engineers can curl, no session required.
curl https://platform.nyks.dev/minis-api.md