API key requests

Search Documentation

Search across all developer documentation

Requesting API keys for sub-agents

An agent orchestrating other agents, or a CI job that spins up a tool, often needs to hand that sub-agent its own SendOps credential: narrower than its own, and short-lived. Passing on your own key gives the sub-agent everything you have. Asking a person to create a key by hand breaks the automation.

An API key request sits between the two. A credential already working in an organization asks for a new key with a subset of its own scopes. An org owner or admin is notified and approves or denies it in the dashboard. Once approved, the same credential claims the key and gets its secret once.

Nothing is minted without a person

A request creates no key. Only an owner or admin approving it in the dashboard does, and they can narrow the scopes or shorten the lifetime but never widen either. No API call or MCP tool approves a request.

The flow

  1. Request. POST /v1/api-key-requests with a name, the scopes, an optional lifetime and a reason. The response is the pending request and a one-time claim_token.
  2. Approve. The organization’s owners and admins get an in-app notification and an email. One of them approves or denies the request on the API Keys settings page. They have 24 hours; after that the request lapses.
  3. Poll. GET /v1/api-key-requests/{id} until status is no longer pending.
  4. Claim. Once approved, POST /v1/api-key-requests/{id}/claim with the claim_token, within one hour of approval. The response carries the new key’s secret, once.

All three calls need the api.api_keys.request scope, and all three must come from the same credential.

Who can request

The caller is an API key holding api.api_keys.request, or an OAuth connection (including an MCP client) whose grant includes it. The scope is never on a key unless someone added it: an owner or admin picks it, as Request API keys, when creating or editing a key in the dashboard, and an OAuth connection has it only if the person connecting approved it on the consent screen.

A client_credentials OAuth token is refused with 422: it acts for no person, so there is no grant to tie the request to, claim it with, or revoke it with. Use an API key, or a user-authorized OAuth connection.

The rules

RuleDetail
Scopes only narrowscopes must be a subset of the caller’s own scopes. Every scope must be one an API key can hold.
No chainsapi.api_keys.request can never be requested, and a minted key can never be given it. A key you obtained this way cannot request keys of its own.
Same environmentThe key is in the caller’s environment: a sk_test_ key or oc_test_ client gets a test key.
Always expiresexpires_in_days defaults to 30 and is at most 90. The lifetime starts when the key is claimed. The approving admin may shorten it.
Cannot be extendedA minted key cannot be rotated or cloned in the dashboard; either would extend a lifetime an admin approved. Request a new key instead.
Request windowA pending request lapses after 24 hours if nobody decides it.
Claim windowAn approved request must be claimed within 1 hour of approval, or it lapses.
One claimThe secret is returned once. A second claim is 409.
Caller must still qualifyAt claim time the calling credential must still be active and still hold every approved scope, and the approving admin must still be an active member able to grant them.
Pending capsAt most 3 pending requests per credential and 5 per organization.
Request rateAt most 10 requests per credential per hour.

Revocation cascades

A minted key is tied to the credential that asked for it:

  • Revoking the requesting API key revokes every key it obtained, in the same transaction, and lapses its open requests.
  • Disconnecting the requesting OAuth connection (from Connected Apps, or by an org admin) does the same for the keys it obtained.
  • Rotating the requesting API key moves its minted keys to the new key, so they stay valid. When the old key’s 24-hour grace ends, the minted keys are untouched.

Each minted key also expires on its own at the end of its approved lifetime. A minted key can be revoked on its own at any time, like any other key.

Worked example

A CI job holds $SENDOPS_KEY, a live key with api.api_keys.request, api.contacts.view and api.lists.view. It wants a read-only key for a reporting sub-agent.

1. Request

curl -X POST https://api.sendops.dev/v1/api-key-requests \
-H "Authorization: Bearer $SENDOPS_KEY" \
-H "Content-Type: application/json" \
-d '{
  "name": "nightly-report-agent",
  "scopes": ["api.contacts.view", "api.lists.view"],
  "expires_in_days": 7,
  "reason": "Read-only access for the nightly list report sub-agent."
}'

201 Created:

{
"id": "0192f1a4-7b3c-7d2e-9f10-2a3b4c5d6e7f",
"status": "pending",
"name": "nightly-report-agent",
"environment": "live",
"scopes": ["api.contacts.view", "api.lists.view"],
"expires_in_days": 7,
"reason": "Read-only access for the nightly list report sub-agent.",
"request_expires_at": "2026-10-16T09:00:00Z",
"created_at": "2026-10-15T09:00:00Z",
"claim_token": "kr_..."
}

Store id and claim_token now. The token is shown only in this response; SendOps keeps only a hash of it.

2. Poll status

curl https://api.sendops.dev/v1/api-key-requests/$REQUEST_ID \
-H "Authorization: Bearer $SENDOPS_KEY"
{
"id": "0192f1a4-7b3c-7d2e-9f10-2a3b4c5d6e7f",
"status": "approved",
"name": "nightly-report-agent",
"environment": "live",
"scopes": ["api.contacts.view", "api.lists.view"],
"approved_scopes": ["api.lists.view"],
"expires_in_days": 7,
"approved_expires_in_days": 3,
"reason": "Read-only access for the nightly list report sub-agent.",
"deny_reason": null,
"request_expires_at": "2026-10-16T09:00:00Z",
"claim_expires_at": "2026-10-15T10:12:00Z",
"created_at": "2026-10-15T09:00:00Z",
"decided_at": "2026-10-15T09:12:00Z"
}

Here the admin narrowed the request: only api.lists.view, for 3 days. Always read approved_scopes and approved_expires_in_days rather than assuming you got what you asked for.

statusMeaningNext
pendingWaiting for an admin.Poll again. A minute or more between polls is plenty; the decision is made by a person.
approvedApproved, possibly narrowed.Claim before claim_expires_at.
deniedAn admin refused it. deny_reason may say why.Stop. Do not re-request the same thing in a loop.
claimedThe key was issued.Nothing; the secret cannot be shown again.
lapsedNobody decided within 24 hours, nobody claimed within an hour of approval, or the requesting credential was revoked.Make a new request if you still need the key.

The status read never returns a secret, and only the credential that made the request can read it: any other caller, even another key in the same organization, gets 404.

3. Claim

curl -X POST https://api.sendops.dev/v1/api-key-requests/$REQUEST_ID/claim \
-H "Authorization: Bearer $SENDOPS_KEY" \
-H "Content-Type: application/json" \
-d "{\"claim_token\": \"$CLAIM_TOKEN\"}"

201 Created:

{
"key": {
  "id": "0192f1b0-1c2d-7e3f-8a9b-0c1d2e3f4a5b",
  "name": "nightly-report-agent",
  "environment": "live",
  "key_prefix": "a1b2c3d4",
  "scopes": ["api.lists.view"],
  "expires_at": "2026-10-18T09:15:00Z"
},
"secret": "sk_live_..."
}

Hand secret straight to the sub-agent’s configuration. In the dashboard the key shows Requested by with the name of the credential that asked.

Errors

Errors are RFC 7807 problem documents, as everywhere else in the API.

StatuscodeWhenWhat to do
403invalid_scopeThe credential lacks api.api_keys.request.An owner or admin adds Request API keys to the key.
404not_foundNo such request, or it was made by a different credential.Use the id from your own request, with the credential that made it.
409conflictClaiming a request that is still pending, was denied, or was already claimed; claiming after the one-hour claim window (the request has lapsed); or the requesting credential has been revoked or has expired. detail says which.GET the request for its status. If it has lapsed, make a new request.
422validation_failedA scope you do not hold, an unknown scope, a scope an API key cannot hold, api.api_keys.request itself, no scopes, expires_in_days outside 1–90, a reason over 500 characters, an invalid claim_token, a client_credentials token, a credential that no longer holds an approved scope at claim, or an approving admin who is no longer able to grant it.Fix the request. The approver cases need a new request.
429rate_limitedMore than 10 requests by this credential in an hour, or the pending caps (3 per credential, 5 per organization) are full. detail says which limit.For the hourly limit, wait for Retry-After. The pending caps carry no Retry-After: wait for an admin to decide the pending requests, or for them to lapse.

The usual per-organization rate limit applies to these endpoints too, and its 429 looks the same; read detail to tell them apart.

Security notes

Treat the claim token like a secret

The claim_token is half of what turns an approved request into a working key. It is useless without the credential that asked, but store it as you would that credential: in memory or your secret store, never in logs, issue comments or commit messages.

  • The secret is shown once. The claim response is sent with Cache-Control: no-store, and SendOps stores only a hash of the key. If you lose it, revoke the key and request another.
  • Do not log the claim response. Write secret straight into the sub-agent’s environment variable or secret store.
  • Ask for the least you need. Request the fewest scopes and the shortest lifetime that do the job. The admin sees your reason; a clear one gets a faster decision.
  • Do not retry a request blindly. Each POST /v1/api-key-requests is a new request, notifies the admins again, and counts toward the pending caps and the hourly limit. If a request call failed in transit, wait a moment and check with the admin before asking again.
  • Plan for revocation. A minted key dies with the credential that asked for it. If your sub-agent must outlive the parent, ask an admin to create a regular key instead.

From an MCP agent

An agent connected over MCP does the same through the api_key_requests tool, with action set to request, status or claim. See MCP Server → API keys.

Endpoints

EndpointDoes
POST /v1/api-key-requestsAsk for a narrower key. Returns the one-time claim_token.
GET /v1/api-key-requests/{id}The request’s status and what was approved. Never a secret.
POST /v1/api-key-requests/{id}/claimIssue the approved key and return its secret, once.