# Issue another pull URL for an inbox

`POST /v1/inboxes/{id}/pull-tokens`

- Authentication: required (Bearer token)
- Required scope: `api.inboxes.manage`

Issues one more credential-free URL onto an inbox that already exists,
and returns it ONCE.

**What a pull URL is.** An address on the fetch host that reads this
inbox's status and messages with no SendOps credential of any kind. You
give it to one of your own users; they never authenticate with SendOps,
because you already did. It is read-only — it cannot delete the inbox,
change it, or reach any other inbox — but ANYONE HOLDING IT READS THE
MAIL. Revoking it is how you take that back.

**Why you would call this rather than reuse the mint's URL.** Only the
token's hash is stored, so the URL returned at mint cannot be looked up
again. Issue a new one for a user who lost the link, or a second one
for a second reader, then revoke whichever you no longer want.

Ten live URLs per inbox at most; the eleventh is `422` with code
`pull_token_limit`. Revoked ones do not count.

The body is optional. `label` is a note for telling several URLs apart
and is never matched against anything — do not put your user's email in
it, which is what `account_ref` exists to avoid.

An expired inbox is `410`: there is no mail left to read, so a URL onto
it would open nothing. A deleted one, or one this credential cannot
see, is `404`.

## Path parameters

- `id` (string<uuid>, required) — The inbox's UUID, exactly as returned by `POST /v1/inboxes`. A value that is not a UUID is a 404 rather than a 422 — it names nothing, and saying "that is not a valid UUID" would confirm the format of ids that do exist.

## Request body

Content type: `application/json`

```json
{
  "label": "sent to the customer 2026-09-10"
}
```

## Example request

```bash
curl -X POST 'https://api.sendops.dev/v1/inboxes/00000000-0000-0000-0000-000000000000/pull-tokens' \
  -H "Authorization: Bearer $SENDOPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label":"sent to the customer 2026-09-10"}'
```

## Responses

### 201 — The new token and its URL. The URL is in this response and nowhere else, ever.

Content type: `application/json`

```json
{
  "id": "00000000-0000-0000-0000-000000000000",
  "prefix": "pt_ab3kd9",
  "label": "string",
  "created_at": "2026-05-17T20:00:00Z",
  "revoked_at": "2026-05-17T20:00:00Z",
  "pull_url": "https://fetch.sendops.dev/p/pt_ab3k…/messages"
}
```

### 401 — Missing, malformed, or unknown API key

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 403 — Either the credential lacks the required scope (`code: invalid_scope`), or it is bound to the `test` environment and this operation is irreversible (`code: test_environment_forbidden`). Branch on `code`: the first is fixed by granting the scope, the second only by using a live credential. See the "Live and test credentials" section of the API description.

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 404 — Resource not found

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 410 — The inbox expired and its messages have been deleted. The `code` is
`gone`.

This is the expected end of a poll loop, not an error to investigate:
`expires_at` has been in every response for this id since it was
minted. Mint another inbox to receive more mail.

A DELETED inbox answers `404` instead — deletion is the owner asking us
to forget it, so we do not then confirm it existed.

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 422 — Two different refusals share this status, and the `code` separates
them:

- `pull_token_limit` — the inbox already has ten live pull URLs.
  Nothing about the request was wrong; the same call succeeds after
  `DELETE /v1/inboxes/{id}/pull-tokens/{token_id}` frees a slot.
- `validation_failed` — the body was not a JSON object, or `label` was
  too long.

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 429 — Per-org rate limit exceeded

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```

### 500 — Unexpected server-side failure. The `code` is `internal_error`. The
`request_id` field can be quoted to SendOps support to investigate.

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ],
  "attribute_id": "00000000-0000-0000-0000-000000000000",
  "content_hash": "string",
  "differs": [
    "string"
  ]
}
```
