---
title: "Request Pairing"
method: POST
path: "/agent-keys/requests"
tags: ["agent-keys"]
---

# Request Pairing

`POST /agent-keys/requests`

## Request

- Base URL: `https://app.zeno-works.ch` (the document declares no server; this is the origin it was published from)
- URL: `https://app.zeno-works.ch/agent-keys/requests`
- Auth: none declared

**Public** — the agent has no credential yet, which is the point of the flow.

**The body is required now**, because `email` is. It used to be defaulted, on the
grounds that "a required body whose schema has no required property is *unreachable*
over MCP — the generated tool truthfully marks every argument optional, the transport
then sends no body at all, and FastAPI 422s on the missing one." That argument was about
a body with nothing required in it; with a required `email` the generated tool marks it
required, the transport sends it, and the 422 cannot arise.

Returns a **bare** verification URI — no code, no request id, no token — and it has
always been bare, for the reason RFC 8628 §5.2 gives. The typed code that used to be the
other half is gone; the address is what replaces it (see the module docstring).

**Answers identically whether or not the address has an account.** Same status, same
body, same subsequent polling behaviour; a row is written either way and the mail
decision is made *after* the response is computed, so the handler cannot be timed. Only
an address that really has an account gets a notice: we do not mail strangers, and a
public unauthenticated route that did would be an open mailer running on our relay.

## Request body

- PairingRequestIn: An agent asking to be handed a credential. Public — it has none yet. **The body is required now, and `email` with it.** It used to be defaulted, because "a required body whose schema has no required property is unreachable over MCP — the generated tool truthfully marks every argument optional, the transport then sends no body at all, and FastAPI 422s on the missing one." That reasoning was about a body with *nothing* required; with a required `email` the tool's schema marks it required, the transport sends it, and the 422 the default existed to avoid cannot arise. The agent has to ask the user for the address, which is one question and the thing that makes the request attributable at all.
  - `email` string, required
  - `client_name` string

## Response `200`

Successful Response

- PairingRequestOut: **The same answer for every address**, registered or not. A pairing request for an address with no Zeno account gets this shape too — it just never produces a notice, and the send is scheduled after the response either way, so the handler cannot be timed to tell the two apart.
  - `claim_token` string, required
  - `verification_uri` string, required
  - `expires_at` string, date-time, required
  - `poll_interval_seconds` integer, required

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/zeno-works/apis/zeno-api.md) · [All operations](https://skmtc.dev/zeno-works/apis/zeno-api/llms.txt) · [OpenAPI document](https://skmtc.dev/zeno-works/apis/zeno-api/revisions/1eb93dde146c?raw)
