{
  "openapi": "3.0.3",
  "info": {
    "title": "Decoy Agent API",
    "version": "0.1.0",
    "description": "Decoy lets your agent act as a user's password manager and email client — signing in to\nsites, filling out forms, and sending and reading mail from disposable addresses — while\ntheir secrets stay encrypted. Decoy's servers only ever hold encrypted data and can't read a\npassword or a message; the keys live in the user's Decoy app, where they approve what your\nagent may do.\n\n## Connect\n\nWith the MCP server, connecting is automatic: the first call opens an approval screen the\nperson scans in the Decoy app, and every endpoint here becomes a tool — `credential_get`,\n`email_send`, and so on.\n\nOver raw HTTP, use the OAuth 2.0 Device Grant:\n```bash\n# 1. Start the connection. The response includes `verification_uri_complete` —\n#    open THAT branded Decoy page for the user (https://decoys.me/connect?code=…).\n#    It shows the QR and the in-app approval. NEVER render the QR yourself; always\n#    drive the user to the Decoy page so they approve on a surface they can trust.\ncurl -X POST https://decoy-api.vercel.app/oauth/device \\\n  -d 'client_id=my-agent&scope=account.read account.unlock message.read message.unlock'\n\n# 2. Once they approve, exchange the code for a token.\ncurl -X POST https://decoy-api.vercel.app/oauth/token \\\n  -d 'grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=…&client_id=my-agent'\n# Then send:  Authorization: Bearer <token>\n```\nAsk only for what the task needs — the user can narrow it when they approve, and your agent\ncan request more later.\n\n## Once granted, it just works\n\nAfter the user approves access, your agent uses it until the grant expires or they revoke it —\nread a saved password or new mail instantly, no further interaction. To reach something the\nuser hasn't granted yet, ask for it (they approve once); everything already granted needs\nnothing more.\n\nEvery request stays inside what the user granted: the kind of access, and the specific\naccounts and inboxes they chose. When something falls outside it, the response tells your\nagent exactly what to request next.\n\n## The tools\n\n| Entity | Read | Write | Delete |\n|---|---|---|---|\n| **Accounts** | `credential_get` | `credential_set` | `credential_delete` |\n| **Decoys** | `decoy_list` | `decoy_create` | `decoy_burn` |\n| **Email** | `inbox_list`, `email_read` | `email_send`, `email_reply` | `email_delete` |\n| **Identity** | `identity_get` | `identity_set`, `identity_update` | `identity_delete` |\n| **Access** | `my_permissions` | `request_access` | — |\n\nPlus `password_generate`. An account is a saved login; a decoy is a disposable email address\nyou can attach to one.\n\n## Examples\n\n**Connect**\nYour agent pairs the first time it's used: it shows the user a link to approve in the Decoy\napp. After that, check what you can do.\n```js\nmy_permissions()   // your live grant: scopes, accounts and inboxes, and for how long\n```\n\n**Read a saved password**\n```js\ncredential_get({ domain: \"github.com\" })   // → { username, password }\n```\n\n**Send an email**\nSending needs access to the inbox. If it isn't in your grant, ask for it — the user approves\non their phone — then send from a decoy address.\n```js\nrequest_access({ kind: \"message\", action: \"write\", resource_id: \"<decoy_id>\" })\nemail_send({ from: \"<decoy_id>\", to: \"team@acme.com\", subject: \"Hi\", body_text: \"Sent from a decoy.\" })\n```\n\n**Create a new account**\nSave a new login. The password is encrypted before it leaves your agent.\n```js\ncredential_set({ domain: \"acme.com\", username: \"me@decoys.me\", password: password_generate() })\n```\n",
    "contact": {
      "name": "Decoy",
      "url": "https://decoys.me"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://decoys.me/terms"
    }
  },
  "servers": [
    {
      "url": "https://decoy-api.vercel.app",
      "description": "Production"
    },
    {
      "url": "https://dev-api.decoys.me",
      "description": "Development"
    }
  ],
  "security": [
    {
      "agentAuth": []
    }
  ],
  "tags": [
    {
      "name": "Connect",
      "description": "Connect an agent and manage its token. The person approves in the Decoy app."
    },
    {
      "name": "Accounts",
      "description": "Saved logins. Read, save, and delete a password. To read an account's mail, see its decoy under **Email**."
    },
    {
      "name": "Accounts · advanced",
      "description": "Attach or burn a decoy email, set the password on its own, or check whether a password is saved."
    },
    {
      "name": "Decoys",
      "description": "Disposable email addresses. Create one, list them, or burn one."
    },
    {
      "name": "Decoys · advanced",
      "description": "Enable, disable, rename, or get stats for a decoy; act on several at once; manage notifications and a personal alias domain."
    },
    {
      "name": "Email",
      "description": "Mail to and from your decoy addresses. List the inbox, read a message, send, reply, or delete."
    },
    {
      "name": "Email · advanced",
      "description": "Mark mail read, set up forwarding, get webhooks, and download attachments."
    },
    {
      "name": "Identity",
      "description": "Saved autofill details — emails, phones, cards, addresses, name, birthday. Read, save, update, or delete a field."
    },
    {
      "name": "Access",
      "description": "See what the agent can do and ask for more. `my_permissions` shows the live grant; `request_access` asks the user for new access."
    },
    {
      "name": "Access · advanced",
      "description": "One-off approvals and the user's own management of what they've granted."
    }
  ],
  "x-tagGroups": [
    {
      "name": "Connect",
      "tags": [
        "Connect"
      ]
    },
    {
      "name": "Accounts",
      "tags": [
        "Accounts",
        "Accounts · advanced"
      ]
    },
    {
      "name": "Decoys",
      "tags": [
        "Decoys",
        "Decoys · advanced"
      ]
    },
    {
      "name": "Email",
      "tags": [
        "Email",
        "Email · advanced"
      ]
    },
    {
      "name": "Identity",
      "tags": [
        "Identity"
      ]
    },
    {
      "name": "Access",
      "tags": [
        "Access",
        "Access · advanced"
      ]
    }
  ],
  "paths": {
    "/api/agent/decoys": {
      "get": {
        "operationId": "decoy_list",
        "summary": "List Decoys",
        "description": "List decoy identities with filtering, sorting, and cursor pagination.\n\n**Permissions needed:** scope `decoy.read`.",
        "tags": [
          "Decoys"
        ],
        "x-scope": "decoy.read",
        "x-scope-legacy": "decoys:read",
        "x-fires-push": false,
        "x-mcp-tool": "decoy_list",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Order"
          },
          {
            "name": "sort_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "updated_at"
              ],
              "default": "created_at"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "inactive",
                "all"
              ],
              "default": "active"
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Search agent_label",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/DateAfter"
          },
          {
            "$ref": "#/components/parameters/DateBefore"
          }
        ],
        "responses": {
          "200": {
            "description": "List of decoys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "decoys": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Decoy"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/decoys' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"decoy_list\",\n  \"arguments\": {\n    \"limit\": 0,\n    \"cursor\": \"string\",\n    \"order\": \"asc\",\n    \"sort_by\": \"created_at\",\n    \"status\": \"active\",\n    \"search\": \"string\",\n    \"date_after\": \"2026-01-01T00:00:00Z\",\n    \"date_before\": \"2026-01-01T00:00:00Z\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/decoys', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "post": {
        "operationId": "decoy_create",
        "summary": "Create Decoy",
        "description": "**This is how you set up an alias to Send an email from.** Create a disposable email\nidentity so you can send from a decoy address instead of your real one. The server\ngenerates a privacy-safe alias (e.g. `bright.quiet.stone42@decoys.me`) and encrypts\nmetadata with the user's RSA public key. Next step: `message_send`.\n\n**Permissions needed:** scope `decoy.write`.",
        "tags": [
          "Decoys"
        ],
        "x-scope": "decoy.write",
        "x-scope-legacy": "decoys:write",
        "x-fires-push": false,
        "x-mcp-tool": "decoy_create",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "service_name"
                ],
                "properties": {
                  "service_name": {
                    "type": "string",
                    "description": "Name of the service (e.g. \"Nike\")"
                  },
                  "service_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "label": {
                    "type": "string",
                    "description": "Plaintext agent reference tag (e.g. \"nike-promo\")"
                  },
                  "avatar": {
                    "type": "string",
                    "description": "Avatar variant; random if omitted"
                  },
                  "forward_enabled": {
                    "type": "boolean",
                    "default": false
                  },
                  "forwarding_email_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Decoy created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Decoy"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/decoys' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"service_name\": \"string\",\n  \"service_url\": \"string\",\n  \"label\": \"string\",\n  \"avatar\": \"string\",\n  \"forward_enabled\": true,\n  \"forwarding_email_id\": \"00000000-0000-0000-0000-000000000000\"\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"decoy_create\",\n  \"arguments\": {\n    \"service_name\": \"string\",\n    \"service_url\": \"string\",\n    \"label\": \"string\",\n    \"avatar\": \"string\",\n    \"forward_enabled\": true,\n    \"forwarding_email_id\": \"00000000-0000-0000-0000-000000000000\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/decoys', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"service_name\": \"string\",\n  \"service_url\": \"string\",\n  \"label\": \"string\",\n  \"avatar\": \"string\",\n  \"forward_enabled\": true,\n  \"forwarding_email_id\": \"00000000-0000-0000-0000-000000000000\"\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/decoys/{decoyId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DecoyId"
        }
      ],
      "get": {
        "operationId": "decoy_get",
        "summary": "Read Decoy",
        "description": "Fetch a single decoy the agent's user owns, by ID. Returns non-sensitive metadata only — the masked email alias, status, forwarding flag, and agent label — never the E2EE-encrypted service name or avatar. Use to confirm a decoy exists or read its current state before updating, pausing, or burning it; 404 if the ID isn't owned by the authenticated user.\n\n**Permissions needed:** scope `decoy.read`.",
        "tags": [
          "Decoys · advanced"
        ],
        "x-scope": "decoy.read",
        "x-scope-legacy": "decoys:read",
        "x-fires-push": false,
        "x-mcp-tool": "decoy_get",
        "responses": {
          "200": {
            "description": "Decoy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Decoy"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/decoys/{decoyId}' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"decoy_get\",\n  \"arguments\": {\n    \"decoyId\": \"00000000-0000-0000-0000-000000000000\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/decoys/{decoyId}', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "patch": {
        "operationId": "decoy_update",
        "summary": "Update Decoy",
        "description": "Update a decoy's avatar, service name, or label. Only provided fields are changed.\nWhen changing avatar or service_name, the server re-encrypts metadata with the\nuser's public key so the iOS app renders correctly.\n\n**Permissions needed:** scope `decoy.write`.",
        "tags": [
          "Decoys · advanced"
        ],
        "x-scope": "decoy.write",
        "x-scope-legacy": "decoys:write",
        "x-fires-push": false,
        "x-mcp-tool": "decoy_update",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "service_name": {
                    "type": "string"
                  },
                  "avatar": {
                    "type": "string"
                  },
                  "label": {
                    "type": "string",
                    "nullable": true
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "inactive"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "decoy_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "updated": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X PATCH 'https://decoy-api.vercel.app/api/agent/decoys/{decoyId}' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"service_name\": \"string\",\n  \"avatar\": \"string\",\n  \"label\": \"string\",\n  \"status\": \"active\"\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"decoy_update\",\n  \"arguments\": {\n    \"decoyId\": \"00000000-0000-0000-0000-000000000000\",\n    \"service_name\": \"string\",\n    \"avatar\": \"string\",\n    \"label\": \"string\",\n    \"status\": \"active\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/decoys/{decoyId}', {\n  method: 'PATCH',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"service_name\": \"string\",\n  \"avatar\": \"string\",\n  \"label\": \"string\",\n  \"status\": \"active\"\n}),\n})"
          }
        ]
      },
      "delete": {
        "operationId": "decoy_burn",
        "summary": "Burn Decoy",
        "description": "Permanently delete a decoy and all its data. **Irreversible.**\nUse `PATCH` with `status: inactive` for a reversible pause.\nEmits `decoy.burned` webhook.\n\n**Permissions needed:** scope `decoy.write`.",
        "tags": [
          "Decoys"
        ],
        "x-scope": "decoy.write",
        "x-scope-legacy": "decoys:write",
        "x-fires-push": false,
        "x-mcp-tool": "decoy_burn",
        "responses": {
          "200": {
            "description": "Burned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "decoy_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "deleted": {
                      "type": "boolean"
                    },
                    "burned_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X DELETE 'https://decoy-api.vercel.app/api/agent/decoys/{decoyId}' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"decoy_burn\",\n  \"arguments\": {\n    \"decoyId\": \"00000000-0000-0000-0000-000000000000\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/decoys/{decoyId}', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/agent/decoys/batch/burn": {
      "post": {
        "operationId": "decoy_burn_batch",
        "summary": "Burn Multiple Decoys",
        "description": "Permanently delete up to 20 decoys in one call. Irreversible.\n\n**Permissions needed:** scope `decoy.write`.",
        "tags": [
          "Decoys · advanced"
        ],
        "x-scope": "decoy.write",
        "x-scope-legacy": "decoys:write",
        "x-fires-push": false,
        "x-mcp-tool": "decoy_burn_batch",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "decoy_ids"
                ],
                "properties": {
                  "decoy_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 20
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/decoys/batch/burn' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"decoy_ids\": [\n    \"00000000-0000-0000-0000-000000000000\"\n  ]\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"decoy_burn_batch\",\n  \"arguments\": {\n    \"decoy_ids\": [\n      \"00000000-0000-0000-0000-000000000000\"\n    ]\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/decoys/batch/burn', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"decoy_ids\": [\n    \"00000000-0000-0000-0000-000000000000\"\n  ]\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/decoys/batch/pause": {
      "post": {
        "operationId": "decoy_pause_batch",
        "summary": "Pause Multiple Decoys",
        "description": "Disable inbound mail on up to 20 decoys. Reversible.\n\n**Permissions needed:** scope `decoy.write`.",
        "tags": [
          "Decoys · advanced"
        ],
        "x-scope": "decoy.write",
        "x-scope-legacy": "decoys:write",
        "x-fires-push": false,
        "x-mcp-tool": "decoy_pause_batch",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "decoy_ids"
                ],
                "properties": {
                  "decoy_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 20
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/decoys/batch/pause' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"decoy_ids\": [\n    \"00000000-0000-0000-0000-000000000000\"\n  ]\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"decoy_pause_batch\",\n  \"arguments\": {\n    \"decoy_ids\": [\n      \"00000000-0000-0000-0000-000000000000\"\n    ]\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/decoys/batch/pause', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"decoy_ids\": [\n    \"00000000-0000-0000-0000-000000000000\"\n  ]\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/decoys/{decoyId}/stats": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DecoyId"
        }
      ],
      "get": {
        "operationId": "decoy_stats_get",
        "summary": "Read Decoy Stats",
        "description": "Return aggregate message-activity counters for one decoy: total and unread message counts, last-message timestamp, rolling 7- and 30-day message volumes, and total outbound sends. Use to gauge how busy or active an individual alias is (e.g. to decide whether to pause or burn it) without enumerating its messages. 404 if the decoy isn't owned by you.\n\n**Permissions needed:** scope `decoy.read`.",
        "tags": [
          "Decoys · advanced"
        ],
        "x-scope": "decoy.read",
        "x-scope-legacy": "decoys:read",
        "x-fires-push": false,
        "x-mcp-tool": "decoy_stats",
        "responses": {
          "200": {
            "description": "Stats",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DecoyStats"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/decoys/{decoyId}/stats' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"decoy_stats\",\n  \"arguments\": {\n    \"decoyId\": \"00000000-0000-0000-0000-000000000000\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/decoys/{decoyId}/stats', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/agent/decoys/{decoyId}/notifications": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DecoyId"
        }
      ],
      "get": {
        "operationId": "decoy_notifications_get",
        "summary": "Read Decoy Notifications",
        "description": "Read the per-decoy push-notification preference, returning whether `notify_all_messages` is enabled for this alias (defaults to false). Use to check whether the iOS app currently alerts on every message for this decoy before toggling the setting. 404 if the decoy isn't owned by you.\n\n**Permissions needed:** scope `decoy.read`.",
        "tags": [
          "Decoys · advanced"
        ],
        "x-scope": "decoy.read",
        "x-scope-legacy": "decoys:read",
        "x-fires-push": false,
        "x-mcp-tool": "decoy_notifications_get",
        "responses": {
          "200": {
            "description": "Settings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "decoy_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "notify_all_messages": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/decoys/{decoyId}/notifications' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"decoy_notifications_get\",\n  \"arguments\": {\n    \"decoyId\": \"00000000-0000-0000-0000-000000000000\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/decoys/{decoyId}/notifications', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "patch": {
        "operationId": "decoy_notifications_update",
        "summary": "Update Decoy Notifications",
        "description": "Set the per-decoy `notify_all_messages` flag, controlling whether the user's device receives a push for every message that arrives on this alias. Use to opt a noisy or important decoy in or out of full notifications; returns the decoy ID and the saved boolean. 404 if the decoy isn't owned by you.\n\n**Permissions needed:** scope `decoy.write`.",
        "tags": [
          "Decoys · advanced"
        ],
        "x-scope": "decoy.write",
        "x-scope-legacy": "decoys:write",
        "x-fires-push": false,
        "x-mcp-tool": "decoy_notifications_update",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "notify_all_messages"
                ],
                "properties": {
                  "notify_all_messages": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "decoy_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "notify_all_messages": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X PATCH 'https://decoy-api.vercel.app/api/agent/decoys/{decoyId}/notifications' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"notify_all_messages\": true\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"decoy_notifications_update\",\n  \"arguments\": {\n    \"decoyId\": \"00000000-0000-0000-0000-000000000000\",\n    \"notify_all_messages\": true\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/decoys/{decoyId}/notifications', {\n  method: 'PATCH',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"notify_all_messages\": true\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/inbox": {
      "get": {
        "operationId": "message_list",
        "summary": "List Inbox",
        "description": "**First half of Read an email.** Lists message headers across all decoys (or a\nfiltered set) with sorting and cursor pagination. Returns **metadata only** —\nsender, subject, and body are encrypted; to read them call `message_unlock`\n(**Unlock** `kind: message`). To avoid polling, register a `message.received`\nwebhook (see **Webhooks**) and unlock on each event.\n\n**Permissions needed:** scope `message.read` — returns only the inboxs you've been granted.",
        "tags": [
          "Email"
        ],
        "x-scope": "message.read",
        "x-scope-legacy": "inbox:read",
        "x-fires-push": false,
        "x-mcp-tool": "inbox_list",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Order"
          },
          {
            "name": "decoy_ids",
            "in": "query",
            "description": "Comma-separated UUIDs to filter",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "is_read",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "channel",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "email",
                "sms",
                "call"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "inbound",
                "outbound"
              ]
            }
          },
          {
            "name": "sender_domain",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "received_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "received_before",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Messages",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "messages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MessageMeta"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/inbox' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"inbox_list\",\n  \"arguments\": {\n    \"limit\": 0,\n    \"cursor\": \"string\",\n    \"order\": \"asc\",\n    \"decoy_ids\": \"string\",\n    \"is_read\": true,\n    \"channel\": \"email\",\n    \"direction\": \"inbound\",\n    \"sender_domain\": \"string\",\n    \"received_after\": \"2026-01-01T00:00:00Z\",\n    \"received_before\": \"2026-01-01T00:00:00Z\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/inbox', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/agent/inbox/{decoyId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DecoyId"
        }
      ],
      "get": {
        "operationId": "message_list_for_decoy",
        "summary": "List Decoy Inbox",
        "description": "**The simple per-alias view for Read an email.** Lists the most recent messages (newest first, `limit` 1–50, default 20) for a single decoy you own. Returns **metadata only** (channel, direction, read state, timestamp) — to read the sender, subject, and body, call `message_unlock` (**Unlock** `kind: message`), since those fields are encrypted. The top-level `/api/agent/inbox` offers cross-decoy filtering and cursor pagination. 404 if the decoy isn't owned by you.\n\n**Permissions needed:** scope `message.read` — returns only the inboxs you've been granted.",
        "tags": [
          "Email"
        ],
        "x-scope": "message.read",
        "x-scope-legacy": "inbox:read",
        "x-fires-push": false,
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Messages",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "messages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MessageMeta"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/inbox/{decoyId}' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/inbox/{decoyId}', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/agent/inbox/messages/{messageId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MessageId"
        }
      ],
      "get": {
        "operationId": "message_get",
        "summary": "Read Email Metadata",
        "description": "Fetch metadata for a single message by ID, scoped to decoys you own. Returns channel, direction, read state, timestamp, and threading fields (SMTP message-id, In-Reply-To, References, delivery status) — never the encrypted body. **To read the body, sender, or subject, call `message_unlock` (`kind: message`)** — those fields are E2EE and never returned here. Use this metadata call to inspect headers/delivery status, e.g. before replying. 404 if the message isn't owned by you.\n\n**Permissions needed:** scope `message.read` **and** a `read_metadata` grant on the inbox. No grant yet? Ask with `request_access({ kind: \"message\", action: \"read_metadata\", … })` (or expect `403 INSUFFICIENT_GRANT`).",
        "tags": [
          "Email"
        ],
        "x-scope": "message.read",
        "x-scope-legacy": "inbox:read",
        "x-fires-push": false,
        "x-mcp-tool": "inbox_get",
        "responses": {
          "200": {
            "description": "Message metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageMeta"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/inbox/messages/{messageId}' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"inbox_get\",\n  \"arguments\": {\n    \"messageId\": \"00000000-0000-0000-0000-000000000000\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/inbox/messages/{messageId}', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "patch": {
        "operationId": "message_toggle_read",
        "summary": "Mark Email Read",
        "description": "Toggle a single message's read/unread state via the required `is_read` boolean, scoped to decoys you own. Use to mark a message read after the agent has processed it, or unread to resurface it; returns the updated message metadata. 404 if the message isn't owned by you.\n\n**Permissions needed:** scope `message.write` **and** a `write` grant on the inbox. No grant yet? Ask with `request_access({ kind: \"message\", action: \"write\", … })` (or expect `403 INSUFFICIENT_GRANT`).",
        "tags": [
          "Email · advanced"
        ],
        "x-scope": "message.write",
        "x-scope-legacy": "inbox:write",
        "x-fires-push": false,
        "x-mcp-tool": "message_toggle_read",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "is_read"
                ],
                "properties": {
                  "is_read": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageMeta"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X PATCH 'https://decoy-api.vercel.app/api/agent/inbox/messages/{messageId}' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"is_read\": true\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"message_toggle_read\",\n  \"arguments\": {\n    \"messageId\": \"00000000-0000-0000-0000-000000000000\",\n    \"is_read\": true\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/inbox/messages/{messageId}', {\n  method: 'PATCH',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"is_read\": true\n}),\n})"
          }
        ]
      },
      "delete": {
        "operationId": "message_delete",
        "summary": "Delete Email",
        "description": "Permanently delete a single message, scoped to decoys you own. Use to drop spam or processed mail from a decoy's inbox; returns `{ deleted: true, id }`. The deletion is irreversible and 404s if the message isn't owned by you.\n\n**Permissions needed:** scope `message.write` **and** a `destroy` grant on the inbox. No grant yet? Ask with `request_access({ kind: \"message\", action: \"destroy\", … })` (or expect `403 INSUFFICIENT_GRANT`).",
        "tags": [
          "Email"
        ],
        "x-scope": "message.write",
        "x-scope-legacy": "inbox:write",
        "x-fires-push": false,
        "x-mcp-tool": "message_delete",
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedResponse"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X DELETE 'https://decoy-api.vercel.app/api/agent/inbox/messages/{messageId}' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"message_delete\",\n  \"arguments\": {\n    \"messageId\": \"00000000-0000-0000-0000-000000000000\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/inbox/messages/{messageId}', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/agent/inbox/send": {
      "post": {
        "operationId": "message_send",
        "summary": "Send Email",
        "description": "**This is how you Send an email.** Send new mail from one of your decoy aliases to one or more recipients (not a threaded reply). Prerequisite: create the alias first with `decoy_create` (or reuse one from `decoy_list`), then pass its `decoy_id`. For a threaded response to a received message, use `message_reply`. Rate limit: 50/hour/user; max 10 recipients (`to`+`cc`+`bcc`).\n\n**Permissions needed:** scope `message.write` **and** a `write` grant on the inbox. No grant yet? Ask with `request_access({ kind: \"message\", action: \"write\", … })` (or expect `403 INSUFFICIENT_GRANT`).",
        "tags": [
          "Email"
        ],
        "x-scope": "message.write",
        "x-scope-legacy": "inbox:write",
        "x-fires-push": false,
        "x-mcp-tool": "message_send",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "decoy_id",
                  "to",
                  "subject"
                ],
                "properties": {
                  "decoy_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "to": {
                    "oneOf": [
                      {
                        "type": "string",
                        "format": "email"
                      },
                      {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "email"
                        }
                      }
                    ]
                  },
                  "cc": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "email"
                    }
                  },
                  "bcc": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "email"
                    }
                  },
                  "subject": {
                    "type": "string"
                  },
                  "body_text": {
                    "type": "string"
                  },
                  "body_html": {
                    "type": "string"
                  },
                  "attachments": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "$ref": "#/components/schemas/OutboundAttachment"
                    },
                    "description": "File attachments (each base64-encoded). Keep combined base64 under ~4MB; larger files are not supported on this path."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/inbox/send' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"decoy_id\": \"00000000-0000-0000-0000-000000000000\",\n  \"to\": \"user@example.com\",\n  \"cc\": [\n    \"user@example.com\"\n  ],\n  \"bcc\": [\n    \"user@example.com\"\n  ],\n  \"subject\": \"string\",\n  \"body_text\": \"string\",\n  \"body_html\": \"string\",\n  \"attachments\": [\n    {\n      \"filename\": \"string\",\n      \"content\": \"string\"\n    }\n  ]\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"message_send\",\n  \"arguments\": {\n    \"decoy_id\": \"00000000-0000-0000-0000-000000000000\",\n    \"to\": \"user@example.com\",\n    \"cc\": [\n      \"user@example.com\"\n    ],\n    \"bcc\": [\n      \"user@example.com\"\n    ],\n    \"subject\": \"string\",\n    \"body_text\": \"string\",\n    \"body_html\": \"string\",\n    \"attachments\": [\n      {\n        \"filename\": \"string\",\n        \"content\": \"string\"\n      }\n    ]\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/inbox/send', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"decoy_id\": \"00000000-0000-0000-0000-000000000000\",\n  \"to\": \"user@example.com\",\n  \"cc\": [\n    \"user@example.com\"\n  ],\n  \"bcc\": [\n    \"user@example.com\"\n  ],\n  \"subject\": \"string\",\n  \"body_text\": \"string\",\n  \"body_html\": \"string\",\n  \"attachments\": [\n    {\n      \"filename\": \"string\",\n      \"content\": \"string\"\n    }\n  ]\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/inbox/messages/{messageId}/reply": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MessageId"
        }
      ],
      "post": {
        "operationId": "message_reply",
        "summary": "Reply Email",
        "description": "**This is how you Reply to an email** — a threaded send from the same decoy alias, with `In-Reply-To` and `References` headers preserved so it lands in the existing conversation. For a brand-new email, use `message_send`.\n\n**Permissions needed:** scope `message.write` **and** a `write` grant on the inbox. No grant yet? Ask with `request_access({ kind: \"message\", action: \"write\", … })` (or expect `403 INSUFFICIENT_GRANT`).",
        "tags": [
          "Email"
        ],
        "x-scope": "message.write",
        "x-scope-legacy": "inbox:write",
        "x-fires-push": false,
        "x-mcp-tool": "message_reply",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body_text": {
                    "type": "string"
                  },
                  "body_html": {
                    "type": "string"
                  },
                  "subject": {
                    "type": "string"
                  },
                  "to": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "email"
                    }
                  },
                  "cc": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "email"
                    }
                  },
                  "bcc": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "email"
                    }
                  },
                  "attachments": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "$ref": "#/components/schemas/OutboundAttachment"
                    },
                    "description": "File attachments (each base64-encoded). Keep combined base64 under ~4MB; larger files are not supported on this path."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Replied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/inbox/messages/{messageId}/reply' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"body_text\": \"string\",\n  \"body_html\": \"string\",\n  \"subject\": \"string\",\n  \"to\": [\n    \"user@example.com\"\n  ],\n  \"cc\": [\n    \"user@example.com\"\n  ],\n  \"bcc\": [\n    \"user@example.com\"\n  ],\n  \"attachments\": [\n    {\n      \"filename\": \"string\",\n      \"content\": \"string\"\n    }\n  ]\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"message_reply\",\n  \"arguments\": {\n    \"messageId\": \"00000000-0000-0000-0000-000000000000\",\n    \"body_text\": \"string\",\n    \"body_html\": \"string\",\n    \"subject\": \"string\",\n    \"to\": [\n      \"user@example.com\"\n    ],\n    \"cc\": [\n      \"user@example.com\"\n    ],\n    \"bcc\": [\n      \"user@example.com\"\n    ],\n    \"attachments\": [\n      {\n        \"filename\": \"string\",\n        \"content\": \"string\"\n      }\n    ]\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/inbox/messages/{messageId}/reply', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"body_text\": \"string\",\n  \"body_html\": \"string\",\n  \"subject\": \"string\",\n  \"to\": [\n    \"user@example.com\"\n  ],\n  \"cc\": [\n    \"user@example.com\"\n  ],\n  \"bcc\": [\n    \"user@example.com\"\n  ],\n  \"attachments\": [\n    {\n      \"filename\": \"string\",\n      \"content\": \"string\"\n    }\n  ]\n}),\n})"
          }
        ]
      }
    },
    "/api/messages/{messageId}/attachments/{attachmentId}": {
      "get": {
        "operationId": "message_attachment_get",
        "summary": "Download Attachment",
        "description": "Fetch one end-to-end-encrypted attachment blob for a message, authenticated with the user's app session (JWT). Returns the attachment ciphertext exactly as stored — the server never holds plaintext. The device decrypts it with its private key (same RSA-OAEP + AES-256-GCM envelope as the message body) to recover the raw file bytes. Filename and content type are NOT returned here: they live in the message's E2EE `attachments[]` metadata, which the app already has from decrypting the message, so the inbox list stays lean and bytes download only when the user opens an attachment. First-party app endpoint — not part of the agent tool surface.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Email · advanced"
        ],
        "security": [
          {
            "userAuth": []
          }
        ],
        "x-first-party": true,
        "x-fires-push": false,
        "parameters": [
          {
            "name": "messageId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "attachmentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Encrypted attachment blob",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SealedAttachment"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/messages/{messageId}/attachments/{attachmentId}' \\\n  -H 'Authorization: Bearer $DECOY_USER_TOKEN'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/messages/{messageId}/attachments/{attachmentId}', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/sync": {
      "get": {
        "operationId": "sync_changes",
        "summary": "Sync Changes",
        "description": "JMAP-shaped delta feed: everything that changed for this user since `cursor`, coalesced so each id appears in exactly one bucket. Persist the returned `state` as the next cursor; `hasMore: true` → call again immediately. `cursor` is required — `0` replays only the RETAINED window (90-day prune horizon), so bootstrap consumers must full-fetch state first and then anchor at /api/sync/head. IDs only, never content. First-party + programmatic consumers; user session JWT.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Sync"
        ],
        "security": [
          {
            "userAuth": []
          }
        ],
        "x-first-party": true,
        "x-fires-push": false,
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "required": true,
            "description": "Client-held change-log cursor (\"0\" = bootstrap replay of the retained window).",
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Coalesced changes since the cursor",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "state",
                    "hasMore",
                    "changes"
                  ],
                  "properties": {
                    "state": {
                      "type": "string",
                      "description": "Persist as the next cursor."
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "More pages exist — call again with the new state."
                    },
                    "changes": {
                      "type": "object",
                      "description": "Per-entity id buckets (message / decoy / alert).",
                      "additionalProperties": {
                        "type": "object",
                        "required": [
                          "created",
                          "updated",
                          "destroyed"
                        ],
                        "properties": {
                          "created": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "format": "uuid"
                            }
                          },
                          "updated": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "format": "uuid"
                            }
                          },
                          "destroyed": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "format": "uuid"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "410": {
            "description": "Cursor predates the pruned floor — full-resync (refetch state, re-anchor at /api/sync/head).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/sync' \\\n  -H 'Authorization: Bearer $DECOY_USER_TOKEN'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/sync', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/sync/head": {
      "get": {
        "operationId": "sync_head",
        "summary": "Sync Head",
        "description": "Latest change seq for this user — the cheap \"anything new?\" check for focus polls and push-poke wakeups. Compare to your cursor and only pull /api/sync when it moved; also the anchor point after a full state fetch (bootstrap).\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Sync"
        ],
        "security": [
          {
            "userAuth": []
          }
        ],
        "x-first-party": true,
        "x-fires-push": false,
        "responses": {
          "200": {
            "description": "Latest change seq",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "head"
                  ],
                  "properties": {
                    "head": {
                      "type": "string",
                      "description": "Latest change seq for this user (\"0\" when no changes logged yet)."
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/sync/head' \\\n  -H 'Authorization: Bearer $DECOY_USER_TOKEN'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/sync/head', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/messages/get": {
      "post": {
        "operationId": "messages_get",
        "summary": "Resolve Message Metadata",
        "description": "Batch metadata resolve (JMAP /get counterpart) for message ids from the /api/sync feed: routing metadata only (decoy_id, channel, is_read, received_at), never content — bodies come from the per-decoy thread endpoint. Unknown/foreign/unroutable ids are omitted, not errors, so consumers can resolve permissively after deletions.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Sync"
        ],
        "security": [
          {
            "userAuth": []
          }
        ],
        "x-first-party": true,
        "x-fires-push": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ids"
                ],
                "properties": {
                  "ids": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Routing metadata for the resolvable ids",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "messages"
                  ],
                  "properties": {
                    "messages": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "decoy_id",
                          "channel",
                          "is_read"
                        ],
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "decoy_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "channel": {
                            "type": "string"
                          },
                          "is_read": {
                            "type": "boolean"
                          },
                          "received_at": {
                            "type": "string",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/messages/get' \\\n  -H 'Authorization: Bearer $DECOY_USER_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"ids\": [\n    \"00000000-0000-0000-0000-000000000000\"\n  ]\n}'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/messages/get', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"ids\": [\n    \"00000000-0000-0000-0000-000000000000\"\n  ]\n}),\n})"
          }
        ]
      }
    },
    "/api/messages/read-states": {
      "post": {
        "operationId": "messages_read_states",
        "summary": "Bulk Read States",
        "description": "Idempotent bulk read-state update — replaces per-message loops (mark-all-read on a big decoy was N sequential calls). Unknown or foreign ids are ignored, not errors. Flips flow into the change log via trigger, so other devices converge on their next pull; no-op flips are filtered out and never hit the log.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Sync"
        ],
        "security": [
          {
            "userAuth": []
          }
        ],
        "x-first-party": true,
        "x-fires-push": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "read": {
                    "type": "array",
                    "maxItems": 500,
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "unread": {
                    "type": "array",
                    "maxItems": 500,
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Count of rows that actually changed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "updated"
                  ],
                  "properties": {
                    "updated": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/messages/read-states' \\\n  -H 'Authorization: Bearer $DECOY_USER_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"read\": [\n    \"00000000-0000-0000-0000-000000000000\"\n  ],\n  \"unread\": [\n    \"00000000-0000-0000-0000-000000000000\"\n  ]\n}'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/messages/read-states', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"read\": [\n    \"00000000-0000-0000-0000-000000000000\"\n  ],\n  \"unread\": [\n    \"00000000-0000-0000-0000-000000000000\"\n  ]\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/forwarding-emails": {
      "get": {
        "operationId": "forwarding_email_list",
        "summary": "List Forwarding Addresses",
        "description": "List the real destination addresses that decoys can forward mail to, with verification status and expiry. (Forwarding destinations are the one part of your data the service can read, because it must deliver mail to them.) Use this to find a verified address before enabling forwarding on a decoy.\n\n**Permissions needed:** scope `forwarding.read`.",
        "tags": [
          "Email · advanced"
        ],
        "x-scope": "forwarding.read",
        "x-scope-legacy": "forwarding:read",
        "x-fires-push": false,
        "x-mcp-tool": "forwarding_email_list",
        "responses": {
          "200": {
            "description": "Forwarding emails",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "forwarding_emails": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ForwardingEmail"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/forwarding-emails' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"forwarding_email_list\",\n  \"arguments\": {}\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/forwarding-emails', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "post": {
        "operationId": "forwarding_email_add",
        "summary": "Add Forwarding Address",
        "description": "Verification email is sent before the address can be used. Apple Private Relay addresses are rejected.\n\n**Permissions needed:** scope `forwarding.write`.",
        "tags": [
          "Email · advanced"
        ],
        "x-scope": "forwarding.write",
        "x-scope-legacy": "forwarding:write",
        "x-fires-push": false,
        "x-mcp-tool": "forwarding_email_add",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Added",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "is_verified": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "verification_required": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/forwarding-emails' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"email\": \"user@example.com\"\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"forwarding_email_add\",\n  \"arguments\": {\n    \"email\": \"user@example.com\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/forwarding-emails', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"email\": \"user@example.com\"\n}),\n})"
          }
        ]
      },
      "delete": {
        "operationId": "forwarding_email_delete",
        "summary": "Remove Forwarding Address",
        "description": "Fails with 409 if any decoys are actively forwarding to it.\n\n**Permissions needed:** scope `forwarding.write`.",
        "tags": [
          "Email · advanced"
        ],
        "x-scope": "forwarding.write",
        "x-scope-legacy": "forwarding:write",
        "x-fires-push": false,
        "x-mcp-tool": "forwarding_email_delete",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X DELETE 'https://decoy-api.vercel.app/api/agent/forwarding-emails' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"forwarding_email_delete\",\n  \"arguments\": {\n    \"id\": \"00000000-0000-0000-0000-000000000000\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/forwarding-emails', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/agent/forwarding/{decoyId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DecoyId"
        }
      ],
      "get": {
        "operationId": "forwarding_get",
        "summary": "Read Forwarding State",
        "description": "Returns the current forwarding configuration for a single decoy: whether forwarding is enabled and which forwarding-email id (if any) is attached. Use this to read a decoy's forwarding state before toggling it. Returns 404 if the decoy does not exist or is not owned by you.\n\n**Permissions needed:** scope `forwarding.read`.",
        "tags": [
          "Email · advanced"
        ],
        "x-scope": "forwarding.read",
        "x-scope-legacy": "forwarding:read",
        "x-fires-push": false,
        "x-mcp-tool": "forwarding_get",
        "responses": {
          "200": {
            "description": "Forwarding state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "decoy_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "forward_enabled": {
                      "type": "boolean"
                    },
                    "forwarding_email_id": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/forwarding/{decoyId}' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"forwarding_get\",\n  \"arguments\": {\n    \"decoyId\": \"00000000-0000-0000-0000-000000000000\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/forwarding/{decoyId}', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "put": {
        "operationId": "forwarding_set",
        "summary": "Set Forwarding State",
        "description": "Enable or disable forwarding and attach a verified address.\nState-coherence: `forward_enabled=true` requires a non-null verified\n`forwarding_email_id` (422 `FORWARDING_EMAIL_REQUIRED`).\n\n**Permissions needed:** scope `forwarding.write`.",
        "tags": [
          "Email · advanced"
        ],
        "x-scope": "forwarding.write",
        "x-scope-legacy": "forwarding:write",
        "x-fires-push": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "forward_enabled"
                ],
                "properties": {
                  "forward_enabled": {
                    "type": "boolean"
                  },
                  "forwarding_email_id": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "forward_enabled": {
                      "type": "boolean"
                    },
                    "forwarding_email_id": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X PUT 'https://decoy-api.vercel.app/api/agent/forwarding/{decoyId}' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"forward_enabled\": true,\n  \"forwarding_email_id\": \"00000000-0000-0000-0000-000000000000\"\n}'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/forwarding/{decoyId}', {\n  method: 'PUT',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"forward_enabled\": true,\n  \"forwarding_email_id\": \"00000000-0000-0000-0000-000000000000\"\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/accounts": {
      "get": {
        "operationId": "account_list",
        "summary": "List Accounts",
        "description": "Discover which accounts the user has — service name, username, URL, linked decoy, and whether a password is stored — but **never the passwords themselves**. This is how an agent finds what's there before reading or acting on a specific account (read a password with **Read Account Details** / the unlock flow). Supports cursor pagination, sorting, and filters (status, category, has_decoy, search, date range).\n\n**Permissions needed:** scope `account.read` — returns only the accounts you've been granted.",
        "tags": [
          "Accounts"
        ],
        "x-scope": "account.read",
        "x-scope-legacy": "accounts:read",
        "x-fires-push": false,
        "x-mcp-tool": "account_list",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Order"
          },
          {
            "name": "sort_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "updated_at",
                "service_name"
              ],
              "default": "created_at"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "inactive"
              ]
            }
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "has_decoy",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/DateAfter"
          },
          {
            "$ref": "#/components/parameters/DateBefore"
          }
        ],
        "responses": {
          "200": {
            "description": "Accounts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accounts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Account"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/accounts' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"account_list\",\n  \"arguments\": {\n    \"limit\": 0,\n    \"cursor\": \"string\",\n    \"order\": \"asc\",\n    \"sort_by\": \"created_at\",\n    \"status\": \"active\",\n    \"category\": \"string\",\n    \"has_decoy\": true,\n    \"search\": \"string\",\n    \"date_after\": \"2026-01-01T00:00:00Z\",\n    \"date_before\": \"2026-01-01T00:00:00Z\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/accounts', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "post": {
        "operationId": "account_create",
        "summary": "Save New Account",
        "description": "**This is how you Save credentials.** Store a login in the password vault. At minimum provide `service_name` (e.g. \"GitHub\"); optionally add `username`, URLs, category, tags, notes, two-factor, and structured/custom fields. All values are encrypted before they leave your agent — the server only stores unreadable ciphertext. Optionally link an existing decoy alias via `decoy_id`. To get the password back later, use `account_unlock` (Read credentials).\n\n**Permissions needed:** scope `account.write`.",
        "tags": [
          "Accounts"
        ],
        "x-scope": "account.write",
        "x-scope-legacy": "accounts:write",
        "x-fires-push": false,
        "x-mcp-tool": "account_create",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "service_name"
                ],
                "properties": {
                  "service_name": {
                    "type": "string",
                    "description": "The website or service this login is for — e.g. \"GitHub\", \"Gmail\", \"Bank of America\"."
                  },
                  "service_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "login_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "username": {
                    "type": "string"
                  },
                  "category": {
                    "type": "string"
                  },
                  "notes": {
                    "type": "string"
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "two_factor_enabled": {
                    "type": "boolean"
                  },
                  "agent_label": {
                    "type": "string"
                  },
                  "decoy_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "structured": {
                    "type": "array",
                    "description": "Multi-part typed fields (address, credit-card), encrypted client-side. See StructuredField.",
                    "items": {
                      "$ref": "#/components/schemas/StructuredField"
                    }
                  },
                  "customFields": {
                    "type": "array",
                    "maxItems": 50,
                    "description": "Single-value custom fields (the CustomFieldKind ontology), encrypted client-side. See CustomField.",
                    "items": {
                      "$ref": "#/components/schemas/CustomField"
                    }
                  },
                  "has_password": {
                    "type": "boolean",
                    "description": "Whether this account has a stored password. (The password value itself is retrieved via account_unlock.)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/accounts' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"service_name\": \"string\",\n  \"service_url\": \"string\",\n  \"login_url\": \"string\",\n  \"username\": \"string\",\n  \"category\": \"string\",\n  \"notes\": \"string\",\n  \"tags\": [\n    \"string\"\n  ],\n  \"two_factor_enabled\": true,\n  \"agent_label\": \"string\",\n  \"decoy_id\": \"00000000-0000-0000-0000-000000000000\",\n  \"structured\": [\n    {\n      \"kind\": \"address\",\n      \"label\": \"string\",\n      \"values\": {}\n    }\n  ],\n  \"customFields\": [\n    {\n      \"label\": \"string\",\n      \"value\": \"string\"\n    }\n  ],\n  \"has_password\": true\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"account_create\",\n  \"arguments\": {\n    \"service_name\": \"string\",\n    \"service_url\": \"string\",\n    \"login_url\": \"string\",\n    \"username\": \"string\",\n    \"category\": \"string\",\n    \"notes\": \"string\",\n    \"tags\": [\n      \"string\"\n    ],\n    \"two_factor_enabled\": true,\n    \"agent_label\": \"string\",\n    \"decoy_id\": \"00000000-0000-0000-0000-000000000000\",\n    \"structured\": [\n      {\n        \"kind\": \"address\",\n        \"label\": \"string\",\n        \"values\": {}\n      }\n    ],\n    \"customFields\": [\n      {\n        \"label\": \"string\",\n        \"value\": \"string\"\n      }\n    ],\n    \"has_password\": true\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/accounts', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"service_name\": \"string\",\n  \"service_url\": \"string\",\n  \"login_url\": \"string\",\n  \"username\": \"string\",\n  \"category\": \"string\",\n  \"notes\": \"string\",\n  \"tags\": [\n    \"string\"\n  ],\n  \"two_factor_enabled\": true,\n  \"agent_label\": \"string\",\n  \"decoy_id\": \"00000000-0000-0000-0000-000000000000\",\n  \"structured\": [\n    {\n      \"kind\": \"address\",\n      \"label\": \"string\",\n      \"values\": {}\n    }\n  ],\n  \"customFields\": [\n    {\n      \"label\": \"string\",\n      \"value\": \"string\"\n    }\n  ],\n  \"has_password\": true\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/accounts/{accountId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AccountId"
        }
      ],
      "get": {
        "operationId": "account_get",
        "summary": "Read Account Details",
        "description": "Retrieve a single account by id. Returns its metadata plus encrypted fields (decrypted client-side by the SDK), the linked decoy email, password presence/last-changed indicators, and the decoy email history. Returns 404 if not found or not owned by you.\n\n**Permissions needed:** scope `account.read` **and** a `read_metadata` grant on the account. No grant yet? Ask with `request_access({ kind: \"account\", action: \"read_metadata\", … })` (or expect `403 INSUFFICIENT_GRANT`).",
        "tags": [
          "Accounts"
        ],
        "x-scope": "account.read",
        "x-scope-legacy": "accounts:read",
        "x-fires-push": false,
        "x-mcp-tool": "account_get",
        "responses": {
          "200": {
            "description": "Account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/accounts/{accountId}' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"account_get\",\n  \"arguments\": {\n    \"accountId\": \"00000000-0000-0000-0000-000000000000\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/accounts/{accountId}', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "patch": {
        "operationId": "account_update",
        "summary": "Update Account",
        "description": "Update an account. Provide only the fields you want to change. Set password_changed=true when you rotate the password so password_last_changed is recorded and the user's devices refresh. Returns the updated account.\n\n**Permissions needed:** scope `account.write` **and** a `write` grant on the account. No grant yet? Ask with `request_access({ kind: \"account\", action: \"write\", … })` (or expect `403 INSUFFICIENT_GRANT`).",
        "tags": [
          "Accounts"
        ],
        "x-scope": "account.write",
        "x-scope-legacy": "accounts:write",
        "x-fires-push": false,
        "x-mcp-tool": "account_update",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AccountUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X PATCH 'https://decoy-api.vercel.app/api/agent/accounts/{accountId}' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"service_name\": \"string\",\n  \"service_url\": \"string\",\n  \"login_url\": \"string\",\n  \"username\": \"string\",\n  \"category\": \"string\",\n  \"notes\": \"string\",\n  \"tags\": [\n    \"string\"\n  ],\n  \"two_factor_enabled\": true,\n  \"agent_label\": \"string\",\n  \"status\": \"active\",\n  \"structured\": [\n    {\n      \"kind\": \"address\",\n      \"label\": \"string\",\n      \"values\": {}\n    }\n  ],\n  \"customFields\": [\n    {\n      \"label\": \"string\",\n      \"value\": \"string\"\n    }\n  ],\n  \"has_password\": true,\n  \"password_changed\": true\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"account_update\",\n  \"arguments\": {\n    \"accountId\": \"00000000-0000-0000-0000-000000000000\",\n    \"service_name\": \"string\",\n    \"service_url\": \"string\",\n    \"login_url\": \"string\",\n    \"username\": \"string\",\n    \"category\": \"string\",\n    \"notes\": \"string\",\n    \"tags\": [\n      \"string\"\n    ],\n    \"two_factor_enabled\": true,\n    \"agent_label\": \"string\",\n    \"status\": \"active\",\n    \"structured\": [\n      {\n        \"kind\": \"address\",\n        \"label\": \"string\",\n        \"values\": {}\n      }\n    ],\n    \"customFields\": [\n      {\n        \"label\": \"string\",\n        \"value\": \"string\"\n      }\n    ],\n    \"has_password\": true,\n    \"password_changed\": true\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/accounts/{accountId}', {\n  method: 'PATCH',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"service_name\": \"string\",\n  \"service_url\": \"string\",\n  \"login_url\": \"string\",\n  \"username\": \"string\",\n  \"category\": \"string\",\n  \"notes\": \"string\",\n  \"tags\": [\n    \"string\"\n  ],\n  \"two_factor_enabled\": true,\n  \"agent_label\": \"string\",\n  \"status\": \"active\",\n  \"structured\": [\n    {\n      \"kind\": \"address\",\n      \"label\": \"string\",\n      \"values\": {}\n    }\n  ],\n  \"customFields\": [\n    {\n      \"label\": \"string\",\n      \"value\": \"string\"\n    }\n  ],\n  \"has_password\": true,\n  \"password_changed\": true\n}),\n})"
          }
        ]
      },
      "delete": {
        "operationId": "credential_delete",
        "summary": "Delete Account",
        "description": "Soft-delete an account (status becomes `deleted` and it disappears from reads; reversible server-side). Gated on the **`destroy`** action — its own grant: a read or write grant never authorizes a delete. Returns `{ account_id, deleted: true }`, or 404 if not found / not owned. 403 `INSUFFICIENT_GRANT` (with a `request_access` remediation) when the agent lacks a `destroy` grant on the account.\n\n**Permissions needed:** scope `account.write` **and** a `destroy` grant on the account. No grant yet? Ask with `request_access({ kind: \"account\", action: \"destroy\", … })` (or expect `403 INSUFFICIENT_GRANT`).",
        "tags": [
          "Accounts"
        ],
        "x-scope": "account.write",
        "x-scope-legacy": "accounts:write",
        "x-fires-push": false,
        "x-mcp-tool": "credential_delete",
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "deleted": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X DELETE 'https://decoy-api.vercel.app/api/agent/accounts/{accountId}' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"credential_delete\",\n  \"arguments\": {\n    \"accountId\": \"00000000-0000-0000-0000-000000000000\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/accounts/{accountId}', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/agent/accounts/{accountId}/protect": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AccountId"
        }
      ],
      "post": {
        "operationId": "account_protect",
        "summary": "Add Decoy Email to Account",
        "description": "Attach a decoy alias to an account so it stops exposing the user's real address. Pass decoy_id to link an existing active decoy, or omit it to auto-generate a fresh one. Idempotent when already protected (returns the existing decoy); returns the linked decoy id and email hash.\n\n**Permissions needed:** scope `account.write` **and** a `write` grant on the account. No grant yet? Ask with `request_access({ kind: \"account\", action: \"write\", … })` (or expect `403 INSUFFICIENT_GRANT`).",
        "tags": [
          "Accounts · advanced"
        ],
        "x-scope": "account.write",
        "x-scope-legacy": "accounts:write",
        "x-fires-push": false,
        "x-mcp-tool": "account_protect",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "decoy_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Protected",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "decoy_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "decoy_email_hash": {
                      "type": "string"
                    },
                    "new_decoy_email": {
                      "type": "string",
                      "format": "email",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/accounts/{accountId}/protect' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"decoy_id\": \"00000000-0000-0000-0000-000000000000\"\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"account_protect\",\n  \"arguments\": {\n    \"accountId\": \"00000000-0000-0000-0000-000000000000\",\n    \"decoy_id\": \"00000000-0000-0000-0000-000000000000\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/accounts/{accountId}/protect', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"decoy_id\": \"00000000-0000-0000-0000-000000000000\"\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/accounts/{accountId}/burn-decoy": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AccountId"
        }
      ],
      "post": {
        "operationId": "account_burn_decoy",
        "summary": "Burn Decoy Email",
        "description": "Permanently deletes (burns) the linked decoy. Optionally creates a fresh replacement.\n\n**Permissions needed:** scope `account.write` **and** a `write` grant on the account. No grant yet? Ask with `request_access({ kind: \"account\", action: \"write\", … })` (or expect `403 INSUFFICIENT_GRANT`).",
        "tags": [
          "Accounts · advanced"
        ],
        "x-scope": "account.write",
        "x-scope-legacy": "accounts:write",
        "x-fires-push": false,
        "x-mcp-tool": "account_burn_decoy",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "replace": {
                    "type": "boolean",
                    "default": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Burned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "burned_decoy_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "new_decoy_id": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true
                    },
                    "new_decoy_email": {
                      "type": "string",
                      "format": "email",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/accounts/{accountId}/burn-decoy' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"replace\": true\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"account_burn_decoy\",\n  \"arguments\": {\n    \"accountId\": \"00000000-0000-0000-0000-000000000000\",\n    \"replace\": true\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/accounts/{accountId}/burn-decoy', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"replace\": true\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/accounts/{accountId}/credential": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AccountId"
        }
      ],
      "post": {
        "operationId": "account_credential_set",
        "summary": "Set Account Password",
        "description": "Set or rotate the password on an account. The password is end-to-end\nencrypted client-side and is never returned in normal reads — retrieve it\nwith `account_unlock`. (Most integrations set the password directly via\n`account_create` / `account_update`; this is the credential-only path.)\n\n**Permissions needed:** scope `account.write` **and** a `write` grant on the account. No grant yet? Ask with `request_access({ kind: \"account\", action: \"write\", … })` (or expect `403 INSUFFICIENT_GRANT`).",
        "tags": [
          "Accounts · advanced"
        ],
        "x-scope": "account.write",
        "x-scope-legacy": "accounts:write",
        "x-fires-push": true,
        "x-push-kind": "silent",
        "x-push-type": "credential.update",
        "x-mcp-tool": "account_set_credential",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "encrypted_password"
                ],
                "properties": {
                  "encrypted_password": {
                    "type": "string",
                    "description": "The password, encrypted client-side before sending (the SDK handles this)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "has_password": {
                      "type": "boolean"
                    },
                    "password_last_changed": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/accounts/{accountId}/credential' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"encrypted_password\": \"string\"\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"account_set_credential\",\n  \"arguments\": {\n    \"accountId\": \"00000000-0000-0000-0000-000000000000\",\n    \"encrypted_password\": \"string\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/accounts/{accountId}/credential', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"encrypted_password\": \"string\"\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/accounts/{accountId}/credential/status": {
      "parameters": [
        {
          "$ref": "#/components/parameters/AccountId"
        }
      ],
      "get": {
        "operationId": "account_credential_status",
        "summary": "Check Account Password",
        "description": "Returns metadata only. Never returns plaintext.\n\n**Permissions needed:** scope `account.read` **and** a `read_metadata` grant on the account. No grant yet? Ask with `request_access({ kind: \"account\", action: \"read_metadata\", … })` (or expect `403 INSUFFICIENT_GRANT`).",
        "tags": [
          "Accounts · advanced"
        ],
        "x-scope": "account.read",
        "x-scope-legacy": "accounts:read",
        "x-fires-push": false,
        "x-mcp-tool": "account_credential_status",
        "responses": {
          "200": {
            "description": "Status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "has_password": {
                      "type": "boolean"
                    },
                    "password_last_changed": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/accounts/{accountId}/credential/status' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"account_credential_status\",\n  \"arguments\": {\n    \"accountId\": \"00000000-0000-0000-0000-000000000000\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/accounts/{accountId}/credential/status', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/agent/personal-info": {
      "get": {
        "operationId": "personal_info_list",
        "summary": "List Identity Fields",
        "description": "Returns IDs and timestamps only. Bodies are E2EE.\n\n**Permissions needed:** scope `personal.read` — returns only the identity fields you've been granted.",
        "tags": [
          "Identity"
        ],
        "x-scope": "personal.read",
        "x-scope-legacy": "profile:read",
        "x-fires-push": false,
        "x-mcp-tool": "personal_info_list",
        "responses": {
          "200": {
            "description": "Personal info",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PersonalInfoList"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/personal-info' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"personal_info_list\",\n  \"arguments\": {}\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/personal-info', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "post": {
        "operationId": "personal_info_add",
        "summary": "Save Identity Field",
        "description": "Add a reusable Autofill item — email, phone, card, address, name, or birthday — to the user's identity vault. Values are end-to-end encrypted client-side before they are sent; the server stores only ciphertext it cannot read. Returns the new item's id and type.\n\n**Permissions needed:** scope `personal.write`.",
        "tags": [
          "Identity"
        ],
        "x-scope": "personal.write",
        "x-scope-legacy": "profile:write",
        "x-fires-push": false,
        "x-mcp-tool": "personal_info_add",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "email",
                      "phone",
                      "card",
                      "address",
                      "name",
                      "birthday"
                    ]
                  },
                  "encrypted_data": {
                    "type": "string",
                    "description": "The item's fields, encrypted client-side (the SDK handles this)."
                  },
                  "data": {
                    "type": "object",
                    "description": "Deprecated alternative to encrypted_data; prefer encrypted_data."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Added",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "type": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/personal-info' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"type\": \"email\",\n  \"encrypted_data\": \"string\",\n  \"data\": {}\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"personal_info_add\",\n  \"arguments\": {\n    \"type\": \"email\",\n    \"encrypted_data\": \"string\",\n    \"data\": {}\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/personal-info', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"type\": \"email\",\n  \"encrypted_data\": \"string\",\n  \"data\": {}\n}),\n})"
          }
        ]
      },
      "delete": {
        "operationId": "personal_info_delete",
        "summary": "Delete Identity Field",
        "description": "Permanently deletes a single personal-info item identified by its id and type (email, phone, card, address, name, or birthday), which selects the backing table. Scoped to your user; returns 404 if no matching row exists. Returns the deleted flag, id, and type.\n\n**Permissions needed:** scope `personal.write` **and** a `destroy` grant on the identity field. No grant yet? Ask with `request_access({ kind: \"personal\", action: \"destroy\", … })` (or expect `403 INSUFFICIENT_GRANT`).",
        "tags": [
          "Identity"
        ],
        "x-scope": "personal.write",
        "x-scope-legacy": "profile:write",
        "x-fires-push": false,
        "x-mcp-tool": "personal_info_delete",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "email",
                "phone",
                "card",
                "address",
                "name",
                "birthday"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "type": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X DELETE 'https://decoy-api.vercel.app/api/agent/personal-info' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"personal_info_delete\",\n  \"arguments\": {\n    \"id\": \"00000000-0000-0000-0000-000000000000\",\n    \"type\": \"email\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/personal-info', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "patch": {
        "operationId": "personal_info_update",
        "summary": "Update Identity Field",
        "description": "Update a saved personal-info item's value (same field as Save / Delete). Pass its id + type and the new value as `encrypted_data` (encrypted client-side by the SDK; the server stores ciphertext only). Gated on personal write; 404 if not found or not owned.\n\n**Permissions needed:** scope `personal.write` **and** a `write` grant on the identity field. No grant yet? Ask with `request_access({ kind: \"personal\", action: \"write\", … })` (or expect `403 INSUFFICIENT_GRANT`).",
        "tags": [
          "Identity"
        ],
        "x-scope": "personal.write",
        "x-scope-legacy": "profile:write",
        "x-fires-push": false,
        "x-mcp-tool": "identity_update",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type",
                  "encrypted_data"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "email",
                      "phone",
                      "card",
                      "address",
                      "name",
                      "birthday"
                    ]
                  },
                  "encrypted_data": {
                    "type": "string",
                    "description": "base64; the new value, encrypted client-side"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "boolean",
                      "example": true
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "type": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/InsufficientGrant"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X PATCH 'https://decoy-api.vercel.app/api/agent/personal-info' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"type\": \"email\",\n  \"encrypted_data\": \"string\"\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"identity_update\",\n  \"arguments\": {\n    \"id\": \"00000000-0000-0000-0000-000000000000\",\n    \"type\": \"email\",\n    \"encrypted_data\": \"string\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/personal-info', {\n  method: 'PATCH',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"type\": \"email\",\n  \"encrypted_data\": \"string\"\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/webhooks": {
      "get": {
        "operationId": "webhook_list",
        "summary": "List Webhooks",
        "description": "Lists the user's registered webhook endpoints with their target URL, subscribed events, active flag, last_triggered_at, and failure_count. Supports cursor pagination plus filtering by active state and by a subscribed event name. Use to audit or manage outbound event delivery registrations.\n\n**Permissions needed:** scope `webhook.read`.",
        "tags": [
          "Email · advanced"
        ],
        "x-scope": "webhook.read",
        "x-scope-legacy": "profile:read",
        "x-fires-push": false,
        "x-mcp-tool": "webhook_list",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Order"
          },
          {
            "name": "sort_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "last_triggered_at",
                "failure_count"
              ],
              "default": "created_at"
            }
          },
          {
            "name": "active",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "event",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Webhooks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/webhooks' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"webhook_list\",\n  \"arguments\": {\n    \"limit\": 0,\n    \"cursor\": \"string\",\n    \"order\": \"asc\",\n    \"sort_by\": \"created_at\",\n    \"active\": true,\n    \"event\": \"string\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/webhooks', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "post": {
        "operationId": "webhook_register",
        "summary": "Register Webhook",
        "description": "Register an HTTPS callback for real-time events. Payloads are metadata\nonly — no E2EE content. Delivery is signed with HMAC-SHA256 in\n`X-Decoy-Signature`. Maximum 10 active webhooks per user.\n\n**Permissions needed:** scope `webhook.write`.",
        "tags": [
          "Email · advanced"
        ],
        "x-scope": "webhook.write",
        "x-scope-legacy": "profile:read",
        "x-fires-push": false,
        "x-mcp-tool": "webhook_register",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events",
                  "secret"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "HTTPS URL"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/WebhookEvent"
                    }
                  },
                  "secret": {
                    "type": "string",
                    "minLength": 16,
                    "description": "HMAC-SHA256 signing secret"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/webhooks' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"url\": \"string\",\n  \"events\": [\n    {}\n  ],\n  \"secret\": \"string\"\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"webhook_register\",\n  \"arguments\": {\n    \"url\": \"string\",\n    \"events\": [\n      {}\n    ],\n    \"secret\": \"string\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/webhooks', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"url\": \"string\",\n  \"events\": [\n    {}\n  ],\n  \"secret\": \"string\"\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/webhooks/{webhookId}": {
      "parameters": [
        {
          "name": "webhookId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "delete": {
        "operationId": "webhook_delete",
        "summary": "Delete Webhook",
        "description": "Permanently removes a webhook registration owned by you, stopping future event delivery to it. Returns 404 if the webhook does not exist or belongs to another user. Returns the deleted flag and id.\n\n**Permissions needed:** scope `webhook.write`.",
        "tags": [
          "Email · advanced"
        ],
        "x-scope": "webhook.write",
        "x-scope-legacy": "profile:read",
        "x-fires-push": false,
        "x-mcp-tool": "webhook_delete",
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X DELETE 'https://decoy-api.vercel.app/api/agent/webhooks/{webhookId}' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"webhook_delete\",\n  \"arguments\": {\n    \"webhookId\": \"00000000-0000-0000-0000-000000000000\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/webhooks/{webhookId}', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "patch": {
        "operationId": "webhook_update",
        "summary": "Update Webhook",
        "description": "Change a webhook in place — its delivery URL, event subscriptions, signing secret, or active flag — without re-registering. Send only the fields you want to change; the rest stay as they are. A new URL is checked the same way as on register (HTTPS only, no private addresses). 404 if the webhook isn't yours.\n\n**Permissions needed:** scope `webhook.write`.",
        "tags": [
          "Email · advanced"
        ],
        "x-scope": "webhook.write",
        "x-scope-legacy": "profile:read",
        "x-fires-push": false,
        "x-mcp-tool": "webhook_update",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "New HTTPS delivery URL"
                  },
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string"
                    },
                    "description": "New event subscription list (replaces the old one)"
                  },
                  "secret": {
                    "type": "string",
                    "minLength": 16,
                    "description": "New HMAC signing secret"
                  },
                  "active": {
                    "type": "boolean",
                    "description": "Pause (false) or resume (true) delivery"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X PATCH 'https://decoy-api.vercel.app/api/agent/webhooks/{webhookId}' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"url\": \"string\",\n  \"events\": [\n    \"string\"\n  ],\n  \"secret\": \"string\",\n  \"active\": true\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"webhook_update\",\n  \"arguments\": {\n    \"webhookId\": \"00000000-0000-0000-0000-000000000000\",\n    \"url\": \"string\",\n    \"events\": [\n      \"string\"\n    ],\n    \"secret\": \"string\",\n    \"active\": true\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/webhooks/{webhookId}', {\n  method: 'PATCH',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"url\": \"string\",\n  \"events\": [\n    \"string\"\n  ],\n  \"secret\": \"string\",\n  \"active\": true\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/sealed-secrets": {
      "get": {
        "operationId": "sealed_secrets_list",
        "summary": "Read saved credentials & personal fields",
        "description": "Read saved passwords and personal autofill fields. Returns each covered secret encrypted to\nyour agent key; decrypt it locally with your agent private key.\n\nEach `sealed_envelope` (base64) decrypts to the secret's JSON: `kind: \"account\"` →\n`{ host, username, password }`; `kind: \"personal\"` → the field value (e.g. `{ phone }`).\nEnvelope layout: `[2-byte BE wrapped-key length][RSA-OAEP-SHA256 wrapped AES-256 key]\n[12-byte IV][ciphertext][16-byte GCM tag]`.\n\nOnly secrets a standing grant covers are returned (re-checked per row, so revoke/narrow\ntakes effect immediately).\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Reads"
        ],
        "security": [
          {
            "agentAuth": []
          }
        ],
        "x-fires-push": false,
        "responses": {
          "200": {
            "description": "Encrypted secrets the grant covers (possibly empty)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "kind": {
                            "type": "string",
                            "enum": [
                              "account",
                              "personal"
                            ]
                          },
                          "target_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "datatype": {
                            "type": "string",
                            "nullable": true
                          },
                          "sealed_envelope": {
                            "type": "string",
                            "description": "base64; decrypt with the agent private key"
                          },
                          "source_version": {
                            "type": "integer",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/sealed-secrets' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/sealed-secrets', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/agent/inbox/sealed": {
      "get": {
        "operationId": "inbox_sealed_list",
        "summary": "Read email bodies",
        "description": "Read message bodies. Returns each covered message encrypted to your agent key; decrypt\nit locally. Each `sealed_envelope` (base64) decrypts to `{ from, subject, text, html }`\n(same envelope layout as sealed-secrets). Only inboxes a standing grant covers are\nreturned (re-checked per row).\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Reads"
        ],
        "security": [
          {
            "agentAuth": []
          }
        ],
        "x-fires-push": false,
        "parameters": [
          {
            "name": "decoy_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Limit to one inbox"
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Only mail newer than this"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Max items to return"
          }
        ],
        "responses": {
          "200": {
            "description": "Encrypted message copies the grant covers (possibly empty)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "message_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "decoy_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "received_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "sealed_envelope": {
                            "type": "string",
                            "description": "base64; decrypts to { from, subject, text, html }"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/inbox/sealed' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/inbox/sealed', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/agent/vault-key": {
      "get": {
        "operationId": "vault_key_get",
        "summary": "Get the wrapped vault key (full access)",
        "description": "Returns the account vault key wrapped to your agent public key (base64) — present only for\na **full-access** standing grant. Unwrap with your agent private key (RSA-OAEP-SHA256) to a\n32-byte AES key, then open account metadata blobs locally. 404 if this grant isn't full access.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Reads"
        ],
        "security": [
          {
            "agentAuth": []
          }
        ],
        "x-fires-push": false,
        "responses": {
          "200": {
            "description": "Wrapped vault key",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "wrapped_vault_key": {
                      "type": "string",
                      "description": "base64; RSA-OAEP-SHA256 wrapped 32-byte AES key"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/vault-key' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/vault-key', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/agent/approvals": {
      "post": {
        "operationId": "approval_request",
        "summary": "Request Approval",
        "description": "Creates an approval request and fires an APNs push. On approval the\nresponse carries a 60-second ephemeral token that authorizes the\noriginal action.\n\n**Permissions needed:** scope `decoy.write`.",
        "tags": [
          "Access · advanced"
        ],
        "x-scope": "decoy.write",
        "x-scope-legacy": "decoys:write",
        "x-fires-push": true,
        "x-push-kind": "approval",
        "x-push-type": "approval.request",
        "x-mcp-tool": "request_approval",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action",
                  "description"
                ],
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "decoy.burn",
                      "decoy.burn_batch",
                      "forwarding.change",
                      "webhook.register"
                    ]
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "metadata": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Pending",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApprovalRequest"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/approvals' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"action\": \"decoy.burn\",\n  \"description\": \"string\",\n  \"metadata\": {}\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"request_approval\",\n  \"arguments\": {\n    \"action\": \"decoy.burn\",\n    \"description\": \"string\",\n    \"metadata\": {}\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/approvals', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"action\": \"decoy.burn\",\n  \"description\": \"string\",\n  \"metadata\": {}\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/approvals/{requestId}": {
      "parameters": [
        {
          "name": "requestId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "operationId": "approval_poll",
        "summary": "Poll Approval",
        "description": "Poll the status of a step-up approval request the agent created. Returns the request's current state (pending/approved/denied/expired) and, once approved, the short-lived ephemeral token authorizing the gated action. Use to wait for the user's on-device approval before retrying.\n\n**Permissions needed:** scope `decoy.write`.",
        "tags": [
          "Access · advanced"
        ],
        "x-scope": "decoy.write",
        "x-scope-legacy": "decoys:write",
        "x-fires-push": false,
        "responses": {
          "200": {
            "description": "Status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApprovalRequest"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/approvals/{requestId}' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/approvals/{requestId}', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/agent/subdomain": {
      "get": {
        "operationId": "subdomain_status",
        "summary": "Read Subdomain Status",
        "description": "Returns the status of the user's personal subdomain (the catch-all alias domain): whether one is claimed, whether its catch-all decoy is currently active/enabled, and how many aliases under it are disabled. Use as a precondition check before claiming, toggling, or managing aliases.\n\n**Permissions needed:** scope `decoy.read`.",
        "tags": [
          "Decoys · advanced"
        ],
        "x-scope": "decoy.read",
        "x-scope-legacy": "decoys:read",
        "x-fires-push": false,
        "x-mcp-tool": "subdomain_status",
        "responses": {
          "200": {
            "description": "Status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "has_subdomain": {
                      "type": "boolean"
                    },
                    "enabled": {
                      "type": "boolean"
                    },
                    "disabled_count": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/subdomain' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"subdomain_status\",\n  \"arguments\": {}\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/subdomain', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "put": {
        "operationId": "subdomain_claim",
        "summary": "Claim Subdomain",
        "description": "Subdomain claim is **permanent**. 409 if already taken.\n\n**Permissions needed:** scope `decoy.write`.",
        "tags": [
          "Decoys · advanced"
        ],
        "x-scope": "decoy.write",
        "x-scope-legacy": "decoys:write",
        "x-fires-push": false,
        "x-mcp-tool": "subdomain_claim",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subdomain"
                ],
                "properties": {
                  "subdomain": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 32,
                    "pattern": "^[a-z0-9-]+$"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Claimed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subdomain": {
                      "type": "string"
                    },
                    "claimed": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X PUT 'https://decoy-api.vercel.app/api/agent/subdomain' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"subdomain\": \"string\"\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"subdomain_claim\",\n  \"arguments\": {\n    \"subdomain\": \"string\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/subdomain', {\n  method: 'PUT',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"subdomain\": \"string\"\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/subdomain/toggle": {
      "put": {
        "operationId": "subdomain_toggle",
        "summary": "Toggle Subdomain",
        "description": "Enables or disables catch-all delivery for the user's personal subdomain by flipping the underlying subdomain decoy between active and inactive. Returns 400 if no subdomain has been claimed yet. Returns the new enabled state.\n\n**Permissions needed:** scope `decoy.write`.",
        "tags": [
          "Decoys · advanced"
        ],
        "x-scope": "decoy.write",
        "x-scope-legacy": "decoys:write",
        "x-fires-push": false,
        "x-mcp-tool": "subdomain_toggle",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled"
                ],
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Toggled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X PUT 'https://decoy-api.vercel.app/api/agent/subdomain/toggle' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"enabled\": true\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"subdomain_toggle\",\n  \"arguments\": {\n    \"enabled\": true\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/subdomain/toggle', {\n  method: 'PUT',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"enabled\": true\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/subdomain/disable": {
      "get": {
        "operationId": "subdomain_disabled_list",
        "summary": "List Disabled Aliases",
        "description": "Lists the aliases under the personal subdomain that have been individually disabled (suppressed from the catch-all), returning each entry's opaque id and created_at. Alias names themselves are not returned — they are stored as HMAC hashes. Use to review which sub-aliases are currently blocked.\n\n**Permissions needed:** scope `decoy.read`.",
        "tags": [
          "Decoys · advanced"
        ],
        "x-scope": "decoy.read",
        "x-scope-legacy": "decoys:read",
        "x-fires-push": false,
        "x-mcp-tool": "alias_list_disabled",
        "responses": {
          "200": {
            "description": "Disabled aliases",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "disabled_aliases": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/subdomain/disable' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"alias_list_disabled\",\n  \"arguments\": {}\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/subdomain/disable', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "post": {
        "operationId": "subdomain_alias_disable",
        "summary": "Disable Alias",
        "description": "Disables a specific alias under the personal subdomain so mail to it stops being accepted by the catch-all. The alias is stored as an HMAC hash; requires a claimed subdomain (400 otherwise). Idempotent — re-disabling an already-disabled alias returns success without creating a duplicate.\n\n**Permissions needed:** scope `decoy.write`.",
        "tags": [
          "Decoys · advanced"
        ],
        "x-scope": "decoy.write",
        "x-scope-legacy": "decoys:write",
        "x-fires-push": false,
        "x-mcp-tool": "alias_disable",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "alias"
                ],
                "properties": {
                  "alias": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Disabled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "disabled": {
                      "type": "boolean"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/subdomain/disable' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"alias\": \"string\"\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"alias_disable\",\n  \"arguments\": {\n    \"alias\": \"string\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/subdomain/disable', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"alias\": \"string\"\n}),\n})"
          }
        ]
      },
      "delete": {
        "operationId": "subdomain_alias_enable",
        "summary": "Enable Alias",
        "description": "Re-enables a previously disabled subdomain alias, restoring catch-all delivery to it, by removing its HMAC entry from the disabled list. The alias may be passed via the query param or the request body. Returns 404 if the alias is not currently disabled.\n\n**Permissions needed:** scope `decoy.write`.",
        "tags": [
          "Decoys · advanced"
        ],
        "x-scope": "decoy.write",
        "x-scope-legacy": "decoys:write",
        "x-fires-push": false,
        "x-mcp-tool": "alias_enable",
        "parameters": [
          {
            "name": "alias",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "alias": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Re-enabled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X DELETE 'https://decoy-api.vercel.app/api/agent/subdomain/disable' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"alias\": \"string\"\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"alias_enable\",\n  \"arguments\": {\n    \"alias\": \"string\"\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/subdomain/disable', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"alias\": \"string\"\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/subdomain/alias-stats": {
      "post": {
        "operationId": "subdomain_alias_stats",
        "summary": "Read Alias Stats",
        "description": "HMAC-based lookup. Up to 100 aliases per call.\n\n**Permissions needed:** scope `decoy.read`.",
        "tags": [
          "Decoys · advanced"
        ],
        "x-scope": "decoy.read",
        "x-scope-legacy": "decoys:read",
        "x-fires-push": false,
        "x-mcp-tool": "alias_stats",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "aliases"
                ],
                "properties": {
                  "aliases": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 100
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "alias_stats": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/subdomain/alias-stats' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"aliases\": [\n    \"string\"\n  ]\n}'"
          },
          {
            "lang": "json",
            "label": "Decoy MCP",
            "source": "// MCP tool — call from Claude / Cursor / Codex etc.\n{\n  \"tool\": \"alias_stats\",\n  \"arguments\": {\n    \"aliases\": [\n      \"string\"\n    ]\n  }\n}"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/subdomain/alias-stats', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"aliases\": [\n    \"string\"\n  ]\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/tokens": {
      "get": {
        "operationId": "token_list",
        "summary": "List Tokens",
        "description": "List the agent tokens (Connected Apps) issued for the authenticated user — each token's label, scopes, and expiry/revocation state. Use to audit which agents have access. Never returns the token secret itself.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Connect"
        ],
        "security": [
          {
            "userAuth": []
          }
        ],
        "x-fires-push": false,
        "responses": {
          "200": {
            "description": "Tokens",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tokens": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentTokenMeta"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/tokens' \\\n  -H 'Authorization: Bearer $DECOY_USER_TOKEN'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/tokens', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "post": {
        "operationId": "token_create",
        "summary": "Create Token",
        "description": "Token value is returned **once** in the response body. It is never stored\nserver-side; if lost, mint a new one.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Connect"
        ],
        "security": [
          {
            "userAuth": []
          }
        ],
        "x-fires-push": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "scopes"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 64
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/AgentScope"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "JWT value — store securely; returned once"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentScope"
                      }
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/tokens' \\\n  -H 'Authorization: Bearer $DECOY_USER_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"name\": \"string\",\n  \"scopes\": [\n    {}\n  ]\n}'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/tokens', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"name\": \"string\",\n  \"scopes\": [\n    {}\n  ]\n}),\n})"
          }
        ]
      }
    },
    "/api/agent/tokens/{tokenId}": {
      "parameters": [
        {
          "name": "tokenId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "patch": {
        "operationId": "token_update",
        "summary": "Update Token",
        "description": "Update a connected agent token — narrow its scopes and/or rename it. Supply scopes, name, or both. Scopes can only be narrowed, never broadened. Returns the updated token metadata; 404 if the token isn't owned by you.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Connect"
        ],
        "security": [
          {
            "userAuth": []
          }
        ],
        "x-fires-push": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scopes": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/AgentScope"
                    }
                  },
                  "name": {
                    "type": "string",
                    "description": "Rename the connected app."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentScope"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X PATCH 'https://decoy-api.vercel.app/api/agent/tokens/{tokenId}' \\\n  -H 'Authorization: Bearer $DECOY_USER_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"scopes\": [\n    {}\n  ],\n  \"name\": \"string\"\n}'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/tokens/{tokenId}', {\n  method: 'PATCH',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"scopes\": [\n    {}\n  ],\n  \"name\": \"string\"\n}),\n})"
          }
        ]
      },
      "delete": {
        "operationId": "token_revoke",
        "summary": "Revoke Token",
        "description": "Revoke a connected agent token immediately. The DB is the authority, so the token stops working on its next request. Use to disconnect an agent. 404 if the token isn't owned by you.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Connect"
        ],
        "security": [
          {
            "userAuth": []
          }
        ],
        "x-fires-push": false,
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X DELETE 'https://decoy-api.vercel.app/api/agent/tokens/{tokenId}' \\\n  -H 'Authorization: Bearer $DECOY_USER_TOKEN'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/tokens/{tokenId}', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/agent/policies": {
      "get": {
        "operationId": "policy_list_for_agent",
        "summary": "List Permissions",
        "description": "Returns the set of approved, non-expired policies on this token,\neach with its RFC 9396 authorization_details array. Useful for the\nagent to introspect \"what bundles has the user approved for me?\"\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Access"
        ],
        "x-fires-push": false,
        "responses": {
          "200": {
            "description": "Policies",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "policies": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Policy"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/agent/policies' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/policies', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/api/agent/grant-request": {
      "post": {
        "operationId": "grant_request_create",
        "summary": "Request Access",
        "description": "The partner to a `403 access_denied` (`INSUFFICIENT_GRANT`): when an agent lacks\naccess to a specific account, inbox, or personal-info item, it asks the user for\nstanding access. Creates a **pending** Policy and notifies the user, who approves\n(with a duration) or denies it under `/api/user/policies`. The request persists\nuntil acted on, so a missed notification isn't lost. After approval the agent\nretries the original call.\n\nName the target one of two ways:\n- **`resource_id`** — the exact account/decoy/personal id (from a prior list).\n- **`domain`** — account kind only — the public service hostname (e.g.\n  `\"foreup.com\"`) when the agent knows the service but not whether the user has it.\n  The device resolves the match privately at approval; a non-match becomes a plain\n  \"you don't have this\" with no list leak.\n\nThe token must already hold the **scope** for the requested `(kind, action)`\n(e.g. `account.unlock` for `unlock_credential`) — this asks for the *grant*, not\nthe scope. `unlock_passkey_assert` is not requestable here (passkey grants are\nauthored only at pairing).\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Access"
        ],
        "security": [
          {
            "agentAuth": []
          }
        ],
        "x-fires-push": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "kind",
                  "action"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "account",
                      "message",
                      "personal"
                    ]
                  },
                  "resource_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The exact account/decoy/personal id, when the agent already knows it. Provide this or `domain`."
                  },
                  "domain": {
                    "type": "string",
                    "description": "Account kind only — request by SERVICE (e.g. \"foreup.com\") when the agent knows what it wants but not whether the user has it. The user's device resolves the match privately at approval. Provide this or `resource_id`."
                  },
                  "action": {
                    "description": "The action to request. Valid actions depend on kind: `account` → read_metadata, unlock_credential, write, destroy; `message`/`personal` → read_metadata, unlock_content, write, destroy.",
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/RarAction"
                      }
                    ]
                  },
                  "datatypes": {
                    "type": "array",
                    "description": "Optional field-level narrowing. Omit to cover all field categories of the kind.",
                    "items": {
                      "$ref": "#/components/schemas/RarDatatype"
                    }
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 300,
                    "description": "Shown to the user in the approval prompt."
                  }
                }
              },
              "examples": {
                "byDomain": {
                  "summary": "Read a login's password — request by service domain",
                  "value": {
                    "kind": "account",
                    "domain": "github.com",
                    "action": "unlock_credential",
                    "datatypes": [
                      "basic-auth"
                    ],
                    "reason": "Sign in to GitHub to open the PR you asked for."
                  }
                },
                "byResourceId": {
                  "summary": "Read one inbox's messages — request by id",
                  "value": {
                    "kind": "message",
                    "resource_id": "9b2c4d6e-1f3a-4b5c-8d7e-0a1b2c3d4e5f",
                    "action": "unlock_content",
                    "reason": "Read the verification email for Acme signup."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Pending request created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "request_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "example": "pending"
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Seconds the pending request stays open (600)."
                    }
                  }
                },
                "example": {
                  "request_id": "4d6e9b2c-1f3a-4b5c-8d7e-0a1b2c3d4e5f",
                  "status": "pending",
                  "expires_in": 600
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "An identical request is already pending.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "grant_already_requested"
                    },
                    "code": {
                      "type": "string",
                      "example": "GRANT_ALREADY_REQUESTED"
                    },
                    "request_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/api/agent/grant-request' \\\n  -H 'Authorization: Bearer $DECOY_AGENT_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"kind\": \"account\",\n  \"resource_id\": \"00000000-0000-0000-0000-000000000000\",\n  \"domain\": \"string\",\n  \"action\": \"string\",\n  \"datatypes\": [\n    {}\n  ],\n  \"reason\": \"string\"\n}'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/agent/grant-request', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"kind\": \"account\",\n  \"resource_id\": \"00000000-0000-0000-0000-000000000000\",\n  \"domain\": \"string\",\n  \"action\": \"string\",\n  \"datatypes\": [\n    {}\n  ],\n  \"reason\": \"string\"\n}),\n})"
          }
        ]
      }
    },
    "/api/user/policies": {
      "get": {
        "operationId": "policy_list_for_user",
        "summary": "List User Policies",
        "description": "List the user's RFC 9396 agent-authorization policies (the consent ledger): for each granted bundle, its scopes, target selectors, datatypes, and time-box. Includes pending requests (status `pending`) awaiting a decision. Use to show or audit what standing access has been granted to agents.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Access · advanced"
        ],
        "security": [
          {
            "userAuth": []
          }
        ],
        "x-fires-push": false,
        "parameters": [
          {
            "name": "token_id",
            "in": "query",
            "description": "Filter to one token's policies. Omit for all tokens.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Policies",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "policies": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Policy"
                      }
                    }
                  }
                },
                "example": {
                  "policies": [
                    {
                      "id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
                      "token_id": "8f7e6d5c-4b3a-2918-0716-5a4b3c2d1e0f",
                      "token_name": "Claude Code",
                      "status": "approved",
                      "reason": "Custom access (in-app)",
                      "expires_at": null,
                      "approved_at": "2026-06-20T14:03:00Z",
                      "revoked_at": null,
                      "created_at": "2026-06-20T14:03:00Z",
                      "permissions": [
                        {
                          "type": "https://decoys.me/oauth/types/unlock",
                          "kind": "account",
                          "account_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
                          "actions": [
                            "read_metadata",
                            "unlock_credential"
                          ],
                          "datatypes": [
                            "basic-auth"
                          ]
                        }
                      ]
                    },
                    {
                      "id": "2b3c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
                      "token_id": "8f7e6d5c-4b3a-2918-0716-5a4b3c2d1e0f",
                      "token_name": "Claude Code",
                      "status": "pending",
                      "reason": "Read the verification email for Acme signup.",
                      "expires_at": null,
                      "approved_at": null,
                      "revoked_at": null,
                      "created_at": "2026-06-22T09:15:00Z",
                      "permissions": [
                        {
                          "type": "https://decoys.me/oauth/types/unlock",
                          "kind": "message",
                          "decoy_id": "9b2c4d6e-1f3a-4b5c-8d7e-0a1b2c3d4e5f",
                          "actions": [
                            "unlock_content"
                          ]
                        }
                      ]
                    }
                  ]
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X GET 'https://decoy-api.vercel.app/api/user/policies' \\\n  -H 'Authorization: Bearer $DECOY_USER_TOKEN'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/user/policies', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      },
      "put": {
        "operationId": "policy_replace_access_flow",
        "summary": "Replace Access Flow",
        "description": "Replace the in-app-authored grants for one agent token with the supplied set — the agent's \"access flow\": which accounts, inboxes, and personal-info items it can reach, with which actions, for how long. Empty (or omitted) `resource_ids` on a grant means all resources of that kind. This becomes the token's authoritative flow, retiring the default all-access grant. Leaves request-approved grants untouched.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Access · advanced"
        ],
        "security": [
          {
            "userAuth": []
          }
        ],
        "x-fires-push": false,
        "parameters": [
          {
            "name": "token_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "grants"
                ],
                "properties": {
                  "grants": {
                    "type": "array",
                    "maxItems": 50,
                    "items": {
                      "type": "object",
                      "required": [
                        "kind",
                        "action"
                      ],
                      "properties": {
                        "kind": {
                          "type": "string",
                          "enum": [
                            "account",
                            "message",
                            "personal"
                          ]
                        },
                        "action": {
                          "description": "Valid actions by kind: `account` → read_metadata, unlock_credential, write, destroy; `message`/`personal` → read_metadata, unlock_content, write, destroy.",
                          "allOf": [
                            {
                              "$ref": "#/components/schemas/RarAction"
                            }
                          ]
                        },
                        "resource_ids": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "description": "Empty/omitted = all resources of this kind (wildcard)."
                        }
                      }
                    }
                  },
                  "expires_in_hours": {
                    "type": "number",
                    "description": "Time-box the flow. Omitted = until revoked."
                  }
                }
              },
              "example": {
                "grants": [
                  {
                    "kind": "account",
                    "action": "read_metadata",
                    "resource_ids": []
                  },
                  {
                    "kind": "account",
                    "action": "unlock_credential",
                    "resource_ids": [
                      "7c9e6679-7425-40de-944b-e07fc1f90ae7"
                    ]
                  },
                  {
                    "kind": "message",
                    "action": "unlock_content",
                    "resource_ids": [
                      "9b2c4d6e-1f3a-4b5c-8d7e-0a1b2c3d4e5f"
                    ]
                  }
                ],
                "expires_in_hours": 168
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Replaced",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "token_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "policy_id": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true
                    },
                    "grant_count": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X PUT 'https://decoy-api.vercel.app/api/user/policies' \\\n  -H 'Authorization: Bearer $DECOY_USER_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"grants\": [\n    {\n      \"kind\": \"account\",\n      \"action\": \"string\"\n    }\n  ],\n  \"expires_in_hours\": 0\n}'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/user/policies', {\n  method: 'PUT',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"grants\": [\n    {\n      \"kind\": \"account\",\n      \"action\": \"string\"\n    }\n  ],\n  \"expires_in_hours\": 0\n}),\n})"
          }
        ]
      }
    },
    "/api/user/policies/{policyId}": {
      "parameters": [
        {
          "name": "policyId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "patch": {
        "operationId": "policy_decide",
        "summary": "Decide Access Request",
        "description": "Decide a pending policy (an agent grant-request). `approved` grants it — with a duration via `expires_in_hours` (omitted = until revoked); `denied` rejects it. Only acts on a policy in status `pending`.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Access · advanced"
        ],
        "security": [
          {
            "userAuth": []
          }
        ],
        "x-fires-push": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "status"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "approved",
                      "denied"
                    ]
                  },
                  "expires_in_hours": {
                    "type": "number",
                    "description": "On approve: time-box the grant. Omitted = until revoked."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Decided",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X PATCH 'https://decoy-api.vercel.app/api/user/policies/{policyId}' \\\n  -H 'Authorization: Bearer $DECOY_USER_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"status\": \"approved\",\n  \"expires_in_hours\": 0\n}'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/user/policies/{policyId}', {\n  method: 'PATCH',\n  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"status\": \"approved\",\n  \"expires_in_hours\": 0\n}),\n})"
          }
        ]
      },
      "delete": {
        "operationId": "policy_revoke",
        "summary": "Revoke Policy",
        "description": "Marks the policy revoked. Future unlocks fall back to per-use approval.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Access · advanced"
        ],
        "security": [
          {
            "userAuth": []
          }
        ],
        "x-fires-push": false,
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": {
                      "type": "boolean"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X DELETE 'https://decoy-api.vercel.app/api/user/policies/{policyId}' \\\n  -H 'Authorization: Bearer $DECOY_USER_TOKEN'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/api/user/policies/{policyId}', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${token}` },\n})"
          }
        ]
      }
    },
    "/oauth/device": {
      "post": {
        "operationId": "oauth_device_authorize",
        "summary": "Start Device Authorization",
        "description": "Creates a device-flow session. Accepts `request_uri` from a prior PAR\ncall to attach `authorization_details` to the session, or accepts a\nlegacy `{ name, scopes, … }` body directly.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Connect"
        ],
        "security": [],
        "x-fires-push": false,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "request_uri": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/AgentScope"
                    }
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "redirect_uri": {
                    "type": "string",
                    "format": "uri"
                  },
                  "state": {
                    "type": "string"
                  },
                  "agent_public_key": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Session created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "device_code": {
                      "type": "string"
                    },
                    "user_code": {
                      "type": "string"
                    },
                    "verification_uri": {
                      "type": "string",
                      "format": "uri",
                      "description": "The branded Decoy pairing page, `https://decoys.me/connect`."
                    },
                    "verification_uri_complete": {
                      "type": "string",
                      "format": "uri",
                      "description": "Same page with the code embedded (`…/connect?code=…`) — **open this\nfor the user.** It renders the QR and the in-app approval. Do NOT\nrender the QR yourself; always drive the user to this Decoy page.\n"
                    },
                    "qr_data": {
                      "type": "string",
                      "description": "The deep-link the **decoys.me/connect page** encodes as its QR. Not for\nthe agent to render — showing a raw pairing QR from an arbitrary app trains\nusers to scan untrusted codes. Use `verification_uri_complete` instead.\n"
                    },
                    "interval": {
                      "type": "integer"
                    },
                    "expires_in": {
                      "type": "integer"
                    },
                    "incremental": {
                      "type": "boolean",
                      "description": "True when the request_uri was bound to an existing\nBearer token (Phase D incremental authorization). The\nserver fires an APNs consent push directly to the\npaired user's phone — no QR rescan needed. The agent\nshould suppress its QR rendering and poll normally.\n"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/oauth/device' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"request_uri\": \"string\",\n  \"name\": \"string\",\n  \"scopes\": [\n    {}\n  ],\n  \"client_id\": \"string\",\n  \"redirect_uri\": \"string\",\n  \"state\": \"string\",\n  \"agent_public_key\": \"string\"\n}'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/oauth/device', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"request_uri\": \"string\",\n  \"name\": \"string\",\n  \"scopes\": [\n    {}\n  ],\n  \"client_id\": \"string\",\n  \"redirect_uri\": \"string\",\n  \"state\": \"string\",\n  \"agent_public_key\": \"string\"\n}),\n})"
          }
        ]
      }
    },
    "/oauth/token": {
      "post": {
        "operationId": "oauth_token",
        "summary": "Exchange Device Token",
        "description": "Exchange a `device_code` for an access token. On success returns\n`access_token`, `token_type=Bearer`, `expires_in`, `scope`, and the\ngranted `authorization_details` (RFC 9396) when present.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Connect"
        ],
        "security": [],
        "x-fires-push": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type",
                  "device_code"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "urn:ietf:params:oauth:grant-type:device_code"
                    ]
                  },
                  "device_code": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "access_token": {
                      "type": "string"
                    },
                    "token_type": {
                      "type": "string"
                    },
                    "expires_in": {
                      "type": "integer"
                    },
                    "scope": {
                      "type": "string"
                    },
                    "authorization_details": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Pending / denied / expired / unknown",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "error_description": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/oauth/token' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"grant_type\": \"urn:ietf:params:oauth:grant-type:device_code\",\n  \"device_code\": \"string\"\n}'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/oauth/token', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"grant_type\": \"urn:ietf:params:oauth:grant-type:device_code\",\n  \"device_code\": \"string\"\n}),\n})"
          }
        ]
      }
    },
    "/oauth/revoke": {
      "post": {
        "operationId": "oauth_revoke",
        "summary": "Revoke OAuth Token",
        "description": "Returns 200 unconditionally. Idempotent.\n\n**Permissions needed:** none — public pairing / user-authenticated endpoint.",
        "tags": [
          "Connect"
        ],
        "security": [],
        "x-fires-push": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  },
                  "token_type_hint": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged (revoked if valid; ignored if not)"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://decoy-api.vercel.app/oauth/revoke' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"token\": \"string\",\n  \"token_type_hint\": \"string\"\n}'"
          },
          {
            "lang": "js",
            "label": "JavaScript",
            "source": "await fetch('https://decoy-api.vercel.app/oauth/revoke', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n  \"token\": \"string\",\n  \"token_type_hint\": \"string\"\n}),\n})"
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "agentAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Agent JWT minted via the pairing flow. Send as `Authorization Bearer <token>`."
      },
      "userAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "User access token (login or Apple Sign In). Used only for token management and pairing endpoints."
      }
    },
    "parameters": {
      "Limit": {
        "name": "limit",
        "in": "query",
        "description": "Page size (1-100)",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 20
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "description": "Opaque cursor from a previous `pagination.next_cursor`",
        "schema": {
          "type": "string"
        }
      },
      "Order": {
        "name": "order",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "asc",
            "desc"
          ],
          "default": "desc"
        }
      },
      "DateAfter": {
        "name": "date_after",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "DateBefore": {
        "name": "date_before",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "DecoyId": {
        "name": "decoyId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "MessageId": {
        "name": "messageId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "AccountId": {
        "name": "accountId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid token",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Token lacks the required scope or capability",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "InsufficientGrant": {
        "description": "**Second gate of the access model.** The token holds the required scope but no active Policy grants it access to this specific resource/action. List endpoints return the granted subset instead of this error. Recover by requesting access: `POST /api/agent/grant-request` (by `resource_id`, or `domain` for accounts), wait for the user to approve, then retry the original call.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/GrantError"
            },
            "example": {
              "error": "access_denied",
              "code": "INSUFFICIENT_GRANT",
              "kind": "account",
              "resource_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
              "request_action": "unlock_credential",
              "can_request": true,
              "request_endpoint": "/api/agent/grant-request"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "State conflict",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "Semantically invalid",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "AgentScope": {
        "type": "string",
        "description": "Permission token. The new `resource.verb` names are canonical; legacy\n`resource:verb` names are accepted as aliases.\n",
        "enum": [
          "decoy.read",
          "decoy.write",
          "message.read",
          "message.write",
          "message.unlock",
          "forwarding.read",
          "forwarding.write",
          "account.read",
          "account.write",
          "account.unlock",
          "account.passkey_assert",
          "alert.read",
          "alert.write",
          "profile.read",
          "personal.read",
          "personal.write",
          "personal.unlock",
          "webhook.read",
          "webhook.write",
          "decoys:read",
          "decoys:write",
          "inbox:read",
          "inbox:write",
          "inbox:content",
          "forwarding:read",
          "forwarding:write",
          "accounts:read",
          "accounts:write",
          "alerts:read",
          "alerts:write",
          "profile:read",
          "profile:write"
        ]
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "next_cursor": {
            "type": "string",
            "nullable": true,
            "description": "Pass to subsequent calls. `null` when no more pages."
          },
          "has_more": {
            "type": "boolean"
          },
          "count": {
            "type": "integer"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "details": {
            "type": "object"
          }
        }
      },
      "GrantError": {
        "type": "object",
        "description": "Body of a `403 INSUFFICIENT_GRANT` — the per-resource grant gate denied this call. The token has the scope but no active Policy covering `resource_id` for `request_action`. Recover via `request_endpoint`.",
        "required": [
          "error",
          "code"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "access_denied"
            ]
          },
          "code": {
            "type": "string",
            "enum": [
              "INSUFFICIENT_GRANT"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "account",
              "message",
              "personal"
            ],
            "description": "Resource family the denied call targeted."
          },
          "resource_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "The specific account/decoy/personal id, or null for a list/wildcard context."
          },
          "request_action": {
            "$ref": "#/components/schemas/RarAction"
          },
          "can_request": {
            "type": "boolean",
            "description": "Always true — this denial is recoverable by requesting access."
          },
          "request_endpoint": {
            "type": "string",
            "description": "Where to ask the user for standing access.",
            "example": "/api/agent/grant-request"
          }
        }
      },
      "RarAction": {
        "type": "string",
        "description": "RFC 9396 `actions` value — what a grant authorizes on its target.\n- `read_metadata` — read non-secret fields (metadata, headers, credential presence).\n- `unlock_credential` — release a stored account credential/field (Unlock `kind: account`).\n- `unlock_content` — release a message body or personal field (Unlock `kind: message`/`personal`).\n- `write` — create/edit a resource (never authorizes a destroy).\n- `destroy` — irreversible deletion (never implied by `write`).\n- `unlock_passkey_assert` — sign a passkey assertion (\"log in as the user\"); strict, never wildcard-covered, always Face-ID gated.\n",
        "enum": [
          "read_metadata",
          "unlock_credential",
          "unlock_content",
          "write",
          "destroy",
          "unlock_passkey_assert"
        ]
      },
      "RarDatatype": {
        "type": "string",
        "description": "RFC 9396 `datatypes` value (CXF credential-type name) — narrows a grant or Unlock to a field category. Omitting `datatypes` (NULL) covers all categories of the kind. `passkey` is assertion-only (signed on-device, never returned as a field).",
        "enum": [
          "basic-auth",
          "totp",
          "custom-fields",
          "note",
          "address",
          "credit-card",
          "passkey"
        ]
      },
      "DeletedResponse": {
        "type": "object",
        "properties": {
          "deleted": {
            "type": "boolean"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "BatchResult": {
        "type": "object",
        "properties": {
          "succeeded": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "failed": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "error": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Decoy": {
        "type": "object",
        "example": {
          "id": "3f1a2b3c-4d5e-6f70-8192-a3b4c5d6e7f8",
          "decoy_email": "swift.harbor93@decoys.me",
          "decoy_email_hash": "9f2c1ab4e7d8c3f0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718",
          "account_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
          "service_name": "Acme Store",
          "service_url": "https://acme.com",
          "agent_label": "acme-signup",
          "avatar": "anchor",
          "status": "active",
          "forward_enabled": false,
          "forwarding_email_id": null,
          "created_at": "2026-05-02T11:15:00Z",
          "updated_at": "2026-06-18T08:42:00Z"
        },
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "decoy_email": {
            "type": "string",
            "format": "email",
            "nullable": true,
            "description": "The alias address (e.g. grow.vivid.sparrow@decoys.me). Returned by list/get and create."
          },
          "decoy_email_hash": {
            "type": "string",
            "description": "SHA-256 of the alias local part. Always present on list/get."
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Owning account. Pivot here (account_get/account_unlock) for the readable service name and stored credential."
          },
          "service_name": {
            "type": "string",
            "description": "Human-readable service name. E2EE — only returned in the create response (and to the user's own client), NOT in agent list/get reads. Use the linked account for the name in those."
          },
          "service_url": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "agent_label": {
            "type": "string",
            "nullable": true,
            "description": "Plaintext tag set by you/the user. The only readable handle on a decoy in list/get, and the only field decoy_list search matches."
          },
          "avatar": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "inactive"
            ]
          },
          "forward_enabled": {
            "type": "boolean"
          },
          "forwarding_email_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DecoyStats": {
        "type": "object",
        "example": {
          "decoy_id": "3f1a2b3c-4d5e-6f70-8192-a3b4c5d6e7f8",
          "total_messages": 42,
          "unread_messages": 3,
          "last_message_at": "2026-06-25T16:20:00Z",
          "messages_7d": 5,
          "messages_30d": 18,
          "outbound_count": 7
        },
        "properties": {
          "decoy_id": {
            "type": "string",
            "format": "uuid"
          },
          "total_messages": {
            "type": "integer"
          },
          "unread_messages": {
            "type": "integer"
          },
          "last_message_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "messages_7d": {
            "type": "integer"
          },
          "messages_30d": {
            "type": "integer"
          },
          "outbound_count": {
            "type": "integer"
          }
        }
      },
      "MessageMeta": {
        "type": "object",
        "example": {
          "id": "a1b2c3d4-e5f6-4789-90ab-cdef01234567",
          "decoy_id": "3f1a2b3c-4d5e-6f70-8192-a3b4c5d6e7f8",
          "channel": "email",
          "direction": "inbound",
          "is_read": false,
          "received_at": "2026-06-25T16:20:00Z",
          "smtp_message_id": "<CAD7h2x9@mail.acme.com>",
          "in_reply_to": null,
          "references": null,
          "delivery_status": null,
          "sender_domain": "acme.com"
        },
        "description": "Metadata only. Bodies require `.unlock`.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "decoy_id": {
            "type": "string",
            "format": "uuid"
          },
          "channel": {
            "type": "string",
            "enum": [
              "email",
              "sms",
              "call"
            ]
          },
          "direction": {
            "type": "string",
            "enum": [
              "inbound",
              "outbound"
            ]
          },
          "is_read": {
            "type": "boolean"
          },
          "received_at": {
            "type": "string",
            "format": "date-time"
          },
          "smtp_message_id": {
            "type": "string",
            "nullable": true
          },
          "in_reply_to": {
            "type": "string",
            "nullable": true
          },
          "references": {
            "type": "string",
            "nullable": true
          },
          "delivery_status": {
            "type": "string",
            "enum": [
              "pending",
              "sent",
              "delivered",
              "bounced",
              "failed"
            ],
            "nullable": true
          },
          "sender_domain": {
            "type": "string",
            "nullable": true,
            "description": "Extracted from `smtp_message_id`; plaintext is safe (no body content)."
          }
        }
      },
      "SendResult": {
        "type": "object",
        "properties": {
          "sent": {
            "type": "boolean"
          },
          "message_id": {
            "type": "string",
            "format": "uuid"
          },
          "provider_message_id": {
            "type": "string",
            "nullable": true
          },
          "from": {
            "type": "string",
            "format": "email"
          },
          "to": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "cc": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "bcc": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          }
        }
      },
      "OutboundAttachment": {
        "type": "object",
        "required": [
          "filename",
          "content"
        ],
        "properties": {
          "filename": {
            "type": "string",
            "description": "File name shown to the recipient, e.g. \"invoice.pdf\""
          },
          "content": {
            "type": "string",
            "description": "Base64-encoded file content"
          },
          "content_type": {
            "type": "string",
            "description": "MIME type, e.g. \"application/pdf\" (default application/octet-stream)"
          },
          "content_id": {
            "type": "string",
            "description": "Optional Content-ID for inline cid: references in body_html"
          }
        }
      },
      "AttachmentMeta": {
        "type": "object",
        "description": "Attachment descriptor carried inside a message's E2EE blob (never the bytes). The in-app viewer renders chips from this and fetches the encrypted bytes on demand via GET /api/messages/{id}/attachments/{attachmentId}.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Attachment id — the key used to fetch the encrypted bytes"
          },
          "filename": {
            "type": "string",
            "description": "Original file name, e.g. \"invoice.pdf\""
          },
          "content_type": {
            "type": "string",
            "description": "MIME type, e.g. \"application/pdf\""
          },
          "size": {
            "type": "integer",
            "description": "Raw (pre-encryption) byte size"
          }
        }
      },
      "SealedAttachment": {
        "type": "object",
        "description": "One E2EE attachment blob. `encrypted_data` is the RSA-OAEP+AES-256-GCM encrypted file bytes (decryptable only on-device). Filename and content type live in the message blob's attachments[] metadata (AttachmentMeta).",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "message_id": {
            "type": "string",
            "format": "uuid"
          },
          "byte_size": {
            "type": "integer",
            "description": "Ciphertext byte length (non-sensitive; for progress/quota)"
          },
          "encrypted_data": {
            "type": "string",
            "nullable": true,
            "description": "Base64-encoded encrypted attachment bytes (RSA-OAEP + AES-256-GCM)"
          }
        }
      },
      "ForwardingEmail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "encrypted_email": {
            "type": "string",
            "description": "Base64-encoded RSA-encrypted email address (decryptable on-device only)"
          },
          "is_verified": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "verification_expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "Account": {
        "type": "object",
        "example": {
          "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
          "service_name": "GitHub",
          "service_url": "https://github.com",
          "login_url": "https://github.com/login",
          "username": "alex@example.com",
          "category": "productivity",
          "notes": "Work account — 2FA via authenticator app",
          "tags": [
            "work",
            "dev"
          ],
          "two_factor_enabled": true,
          "status": "active",
          "agent_label": "github-work",
          "decoy_id": "3f1a2b3c-4d5e-6f70-8192-a3b4c5d6e7f8",
          "has_password": true,
          "password_last_changed": "2026-06-20T14:03:00Z",
          "created_at": "2026-01-12T09:30:00Z",
          "updated_at": "2026-06-20T14:03:00Z",
          "email_history": [
            {
              "decoy_id": "3f1a2b3c-4d5e-6f70-8192-a3b4c5d6e7f8",
              "from": "2026-01-12T09:30:00Z",
              "to": null
            }
          ]
        },
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "service_name": {
            "type": "string"
          },
          "service_url": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "login_url": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "username": {
            "type": "string",
            "nullable": true
          },
          "category": {
            "type": "string",
            "nullable": true
          },
          "notes": {
            "type": "string",
            "nullable": true
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "two_factor_enabled": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "inactive"
            ]
          },
          "agent_label": {
            "type": "string",
            "nullable": true
          },
          "decoy_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "has_password": {
            "type": "boolean"
          },
          "password_last_changed": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "email_history": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "decoy_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "from": {
                  "type": "string",
                  "format": "date-time"
                },
                "to": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "AccountUpdate": {
        "type": "object",
        "properties": {
          "service_name": {
            "type": "string"
          },
          "service_url": {
            "type": "string",
            "format": "uri"
          },
          "login_url": {
            "type": "string",
            "format": "uri"
          },
          "username": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "two_factor_enabled": {
            "type": "boolean"
          },
          "agent_label": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "inactive"
            ]
          },
          "structured": {
            "type": "array",
            "description": "Replace the multi-part typed fields (address/credit-card). Omit to leave unchanged; pass [] to clear.",
            "items": {
              "$ref": "#/components/schemas/StructuredField"
            }
          },
          "customFields": {
            "type": "array",
            "maxItems": 50,
            "description": "Replace the single-value custom fields. Omit to leave unchanged; pass [] to clear. See CustomField.",
            "items": {
              "$ref": "#/components/schemas/CustomField"
            }
          },
          "has_password": {
            "type": "boolean",
            "description": "Whether the account has a password."
          },
          "password_changed": {
            "type": "boolean",
            "description": "When true, bumps password_last_changed (the password was set/changed)."
          }
        }
      },
      "CustomField": {
        "type": "object",
        "required": [
          "label",
          "value"
        ],
        "description": "A single-value custom field on an account (the CustomFieldKind ontology). Stored encrypted in the account vault and returned decrypted client-side.",
        "properties": {
          "label": {
            "type": "string",
            "description": "Field name, e.g. \"Recovery code\" or a security question"
          },
          "value": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "name",
              "dob",
              "email",
              "phone",
              "url",
              "number",
              "date",
              "secret",
              "note",
              "security-question",
              "custom"
            ],
            "default": "custom",
            "description": "Field type (matches the iOS CustomFieldKind ontology)."
          },
          "secret": {
            "type": "boolean",
            "default": false,
            "description": "Whether the value is sensitive (concealed/hidden) — PINs, recovery codes, security answers."
          }
        }
      },
      "StructuredField": {
        "type": "object",
        "description": "A multi-part field. kind=address has streetAddress, apt, city, territory (state/region), postalCode, country. kind=credit-card has number, fullName, expiryDate (YYYY-MM), verificationNumber (CVV); for stored cards only the last four digits are retained.",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "address",
              "credit-card"
            ]
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "values": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "Alert": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "decoy_id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string"
          },
          "severity": {
            "type": "string",
            "enum": [
              "info",
              "warning",
              "critical"
            ]
          },
          "title": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "dismissed": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Rule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "decoy_id": {
            "type": "string",
            "format": "uuid"
          },
          "label": {
            "type": "string"
          },
          "pattern": {
            "type": "string"
          },
          "push_enabled": {
            "type": "boolean"
          },
          "is_enabled": {
            "type": "boolean"
          },
          "trigger_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "UserStats": {
        "type": "object",
        "properties": {
          "active_decoys": {
            "type": "integer"
          },
          "disabled_decoys": {
            "type": "integer"
          },
          "total_messages": {
            "type": "integer"
          }
        }
      },
      "PersonalInfoList": {
        "type": "object",
        "properties": {
          "emails": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PersonalInfoItem"
            }
          },
          "phones": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PersonalInfoItem"
            }
          },
          "cards": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PersonalInfoItem"
            }
          },
          "addresses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PersonalInfoItem"
            }
          },
          "names": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PersonalInfoItem"
            }
          },
          "birthdays": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PersonalInfoItem"
            }
          },
          "counts": {
            "type": "object",
            "properties": {
              "emails": {
                "type": "integer"
              },
              "phones": {
                "type": "integer"
              },
              "cards": {
                "type": "integer"
              },
              "addresses": {
                "type": "integer"
              },
              "names": {
                "type": "integer"
              },
              "birthdays": {
                "type": "integer"
              }
            }
          }
        }
      },
      "PersonalInfoItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "enum": [
              "email",
              "phone",
              "card",
              "address",
              "name",
              "birthday"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            }
          },
          "active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_triggered_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "failure_count": {
            "type": "integer"
          }
        }
      },
      "WebhookEvent": {
        "type": "string",
        "enum": [
          "message.received",
          "message.sent",
          "message.delivered",
          "message.bounced",
          "alert.triggered",
          "alert.dismissed",
          "decoy.created",
          "decoy.burned",
          "decoy.disabled",
          "decoy.enabled",
          "decoy.paused",
          "unlock.approved",
          "unlock.denied"
        ]
      },
      "UnlockRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "message",
              "account",
              "personal"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "ready",
              "denied",
              "expired"
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "auto_approved": {
            "type": "boolean",
            "description": "True when a granted policy covered this unlock and the user did\nnot need to approve interactively. The encrypted envelope is still\nreturned to the agent the same way; the difference is that the\nrequest resolved automatically in the background.\n"
          },
          "items": {
            "type": "array",
            "description": "Present only on first fetch when status=ready",
            "items": {
              "type": "object",
              "properties": {
                "target_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "sealed_envelope": {
                  "type": "string",
                  "description": "Base64 envelope (RSA-OAEP-wrapped AES-256-GCM). Decrypt with your agent private key → the content JSON. For kind=message that is `{ from, subject, text, html }`."
                }
              }
            }
          }
        },
        "example": {
          "id": "123e4567-e89b-12d3-a456-426614174000",
          "kind": "message",
          "status": "ready",
          "expires_at": "2026-06-17T20:39:47.333Z",
          "auto_approved": true,
          "items": [
            {
              "target_id": "123e4567-e89b-12d3-a456-426614174000",
              "sealed_envelope": "AAEC9k3rJp…base64… (decrypt with your agent key → { from, subject, text, html })"
            }
          ]
        }
      },
      "ContentRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "denied",
              "expired"
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "message_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "agent_encrypted_content": {
                  "type": "string",
                  "description": "Base64 envelope. RSA-OAEP(AES key) + AES-256-GCM(body)."
                }
              }
            }
          }
        }
      },
      "ApprovalRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "action": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "denied",
              "expired"
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "ephemeral_token": {
            "type": "string",
            "nullable": true,
            "description": "60-second ephemeral token returned on approval"
          }
        }
      },
      "Policy": {
        "type": "object",
        "example": {
          "id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
          "token_id": "8f7e6d5c-4b3a-2918-0716-5a4b3c2d1e0f",
          "token_name": "Claude Code",
          "agent_public_key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...",
          "status": "approved",
          "reason": "Custom access (in-app)",
          "expires_at": "2026-07-26T21:39:00Z",
          "approved_at": "2026-06-26T21:39:00Z",
          "revoked_at": null,
          "created_at": "2026-06-26T21:39:00Z",
          "permissions": [
            {
              "type": "https://decoys.me/oauth/types/unlock",
              "kind": "account",
              "account_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
              "actions": [
                "read_metadata",
                "unlock_credential"
              ],
              "datatypes": [
                "basic-auth"
              ]
            }
          ]
        },
        "description": "One user \"yes\" — a bundle of permissions on a single agent token. `status` `pending` is an unresolved grant-request; `approved` (not expired, not revoked) is what gates resource calls and auto-approves Unlocks.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "token_id": {
            "type": "string",
            "format": "uuid"
          },
          "token_name": {
            "type": "string",
            "nullable": true
          },
          "agent_public_key": {
            "type": "string",
            "nullable": true,
            "description": "The agent's RSA public key (base64 SPKI), or null — so granted content can be encrypted to this agent. Never a private key."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "denied",
              "expired",
              "revoked"
            ]
          },
          "reason": {
            "type": "string",
            "nullable": true,
            "description": "Why the grant exists, e.g. \"Custom access (in-app)\" or an agent's request reason."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Time-box. Null = until revoked."
          },
          "approved_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "revoked_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "permissions": {
            "type": "array",
            "description": "RFC 9396 authorization_details items granted by this policy.",
            "items": {
              "$ref": "#/components/schemas/PolicyPermission"
            }
          }
        }
      },
      "PolicyPermission": {
        "type": "object",
        "description": "One RFC 9396 authorization_detail row. Exactly one target selector is set (`account_id` / `decoy_id` / `personal_id` / `domain`); all-null = wildcard (any resource of `kind`). Null `actions`/`datatypes` = covers all.",
        "required": [
          "type",
          "kind"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "RAR type URI.",
            "example": "https://decoys.me/oauth/types/unlock"
          },
          "kind": {
            "type": "string",
            "enum": [
              "account",
              "message",
              "personal"
            ]
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "Account selector (kind=account)."
          },
          "decoy_id": {
            "type": "string",
            "format": "uuid",
            "description": "Inbox selector (kind=message)."
          },
          "personal_id": {
            "type": "string",
            "format": "uuid",
            "description": "Personal-info selector (kind=personal)."
          },
          "domain": {
            "type": "string",
            "description": "Account-only selector — covers any account whose service/login hostname matches (eTLD-anchored).",
            "example": "github.com"
          },
          "actions": {
            "type": "array",
            "nullable": true,
            "description": "Authorized actions. Null = all actions of this kind.",
            "items": {
              "$ref": "#/components/schemas/RarAction"
            }
          },
          "datatypes": {
            "type": "array",
            "nullable": true,
            "description": "Authorized field categories. Null = all.",
            "items": {
              "$ref": "#/components/schemas/RarDatatype"
            }
          }
        }
      },
      "AgentTokenMeta": {
        "type": "object",
        "example": {
          "id": "8f7e6d5c-4b3a-2918-0716-5a4b3c2d1e0f",
          "name": "Claude Code",
          "scopes": [
            "account.read",
            "account.unlock",
            "message.read",
            "message.unlock"
          ],
          "created_at": "2026-06-26T21:39:00Z",
          "expires_at": "2027-06-26T21:39:00Z",
          "last_used_at": "2026-06-27T09:12:00Z",
          "agent_public_key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."
        },
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AgentScope"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "agent_public_key": {
            "type": "string",
            "nullable": true,
            "description": "The agent's RSA public key (base64 SPKI) registered at pairing, or null if it paired without one. Public key — returned so the device can pre-seal device-free secret/inbox copies to this agent when editing its access. Never a private key."
          },
          "platform": {
            "type": "string",
            "nullable": true,
            "enum": [
              "mcp",
              "oauth",
              "web",
              "extension",
              null
            ],
            "description": "Where this token came from. `web` / `extension` are first-party device logins (\"Linked Devices\" — the user on another screen, full access). `mcp` / `oauth` (and null for legacy tokens) are agent grants (\"Connected Agents\")."
          }
        }
      }
    }
  }
}
