# POST /api/v1/keys

Auth: public

HTML: https://cooperemail.com/docs/reference/auth

## Summary

Without Bearer, create an account and a full-access key (the secret is returned once). With Bearer, mint another key. Body may include name, inbox_id, permissions, and ttl_hours or expires_at. permissions null means full access. Accepted names include draft_write (create, edit, and delete drafts; does not send) and draft_send (send one draft; does not create, edit, or delete drafts). POST and PATCH /api/v1/inboxes/:id/drafts accept message_send or draft_write. DELETE accepts message_delete, message_send, or draft_write. POST .../drafts/:draftId/send accepts message_send or draft_send. A key with only draft_write cannot send mail. A restricted key can only grant a subset of its own permissions and cannot widen inbox scope. Unknown permission names return 400 invalid_permission. Revoke with DELETE /api/v1/keys/:id (works immediately, including the calling key). GET lists keys and never returns the secret.

## Request

```json
{
  "name": "agent-prod",
  "permissions": [
    "message_read",
    "message_send"
  ],
  "ttl_hours": 24
}
```

## Response

The OpenAPI document does not declare a JSON response schema for this call. The summary is the contract.

## Errors

- HTTP 400 invalid_permission

The live `message` is the sentence for that code. Branch on `code`.

```json
{
  "error": {
    "type": "invalid_request",
    "code": "invalid_permission",
    "docs_url": "https://cooperemail.com/docs#invalid_permission"
  }
}
```

## Idempotency

This route does not document Idempotency-Key or client_id.
