{
  "openapi": "3.1.0",
  "info": {
    "title": "Coffrify Public API",
    "version": "1.0.0",
    "summary": "Encrypted file transfers as a service - for developers.",
    "description": "End-to-end encrypted file transfers with webhooks, audit log and SDK in TypeScript.\n\n**Authentication.** All endpoints (except `/v1/welcome`) require `Authorization: Bearer cof_live_…` or `cof_test_…`. Keys are scoped - see `coffrify_api_scopes` for the full list.\n\n**Versioning.** Every response carries `X-Coffrify-Api-Version: 2026-05-14`. Breaking changes ship under a new version with overlap and `Deprecation`/`Sunset` headers.",
    "contact": {
      "name": "Coffrify Support",
      "email": "help@coffrify.com",
      "url": "https://help.coffrify.com"
    },
    "license": {
      "name": "Proprietary",
      "identifier": "Coffrify-1.0"
    },
    "x-coffrify-api-version": "2026-05-14"
  },
  "servers": [
    {
      "url": "https://api.coffrify.com",
      "description": "Production"
    },
    {
      "url": "https://api.coffrify.com/v1",
      "description": "Production (v1 namespace)"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key. Format: `cof_live_<hex>` (production) or `cof_test_<hex>` (sandbox)."
      },
      "publishableKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Publishable intake key, browser-safe. Format: `cip_live_<hex>` / `cip_test_<hex>`. Used only by the front-end SDK on app.coffrify.com, gated by the request Origin. It can create an upload session for ONE intake and read nothing. The matching secret is your cof_ API key with the intake:read scope."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable error code (e.g. `invalid_api_key`)."
              },
              "message": {
                "type": "string"
              },
              "param": {
                "type": "string",
                "nullable": true
              },
              "doc_url": {
                "type": "string",
                "nullable": true
              },
              "request_id": {
                "type": "string",
                "nullable": true
              }
            }
          }
        }
      },
      "Transfer": {
        "type": "object",
        "required": [
          "id",
          "short_code",
          "status",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "short_code": {
            "type": "string"
          },
          "transfer_title": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "expired",
              "deleted",
              "scanning"
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "total_downloads": {
            "type": "integer"
          },
          "max_downloads": {
            "type": "integer",
            "nullable": true
          },
          "scan_status": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "key_prefix": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "environment": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "is_active": {
            "type": "boolean"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "used_count": {
            "type": "integer"
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "revoked_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "is_active": {
            "type": "boolean"
          },
          "success_count": {
            "type": "integer"
          },
          "failure_count": {
            "type": "integer"
          },
          "last_triggered_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_status": {
            "type": "integer",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AuditEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid"
          },
          "actor_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "action": {
            "type": "string"
          },
          "resource_type": {
            "type": "string",
            "nullable": true
          },
          "resource_id": {
            "type": "string",
            "nullable": true
          },
          "ip_address": {
            "type": "string",
            "nullable": true
          },
          "user_agent": {
            "type": "string",
            "nullable": true
          },
          "country_code": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Intake": {
        "type": "object",
        "required": [
          "object",
          "id",
          "name",
          "slug",
          "enabled",
          "created_at"
        ],
        "properties": {
          "object": {
            "const": "intake"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string",
            "description": "URL-safe identifier of the intake inside the workspace."
          },
          "enabled": {
            "type": "boolean"
          },
          "e2e": {
            "type": "object",
            "description": "End-to-end encryption state. When enabled, every document is sealed to `public_key`; Coffrify never sees plaintext.",
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "public_key": {
                "type": "string",
                "nullable": true,
                "description": "X25519 public key (base64url). The SDK seals each per-file AES key to it."
              },
              "activated_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          },
          "allowed_origins": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Browser origins allowed to use the publishable key. `*` allows any (not recommended in production)."
          },
          "reference_required": {
            "type": "boolean",
            "description": "When true, every submission must carry a `client_reference`."
          },
          "metadata_policy": {
            "type": "string",
            "enum": [
              "envelope",
              "opaque"
            ],
            "description": "`envelope`: cleartext key/value metadata accepted (never secrets). `opaque`: metadata dropped."
          },
          "limits": {
            "type": "object",
            "properties": {
              "max_bytes_per_submission": {
                "type": "integer",
                "nullable": true
              },
              "max_files_per_submission": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "IntakeDocument": {
        "type": "object",
        "required": [
          "object",
          "id",
          "intake_id",
          "status",
          "created_at"
        ],
        "properties": {
          "object": {
            "const": "intake_document"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "intake_id": {
            "type": "string",
            "format": "uuid"
          },
          "client_reference": {
            "type": "string",
            "nullable": true,
            "description": "Opaque reference set by the integrator (e.g. `dossier-4567`). Coffrify never learns the real identity behind it."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Cleartext envelope metadata (string/number/bool). Empty when the intake policy is `opaque`."
          },
          "status": {
            "type": "string",
            "description": "e.g. `received`."
          },
          "files_count": {
            "type": "integer"
          },
          "total_size_bytes": {
            "type": "integer"
          },
          "encrypted": {
            "type": "boolean",
            "description": "True when the document is E2E (sealed to the intake key)."
          },
          "source": {
            "type": "string",
            "description": "`sdk`, `api`, etc."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TransferList": {
        "type": "object",
        "required": [
          "object",
          "data",
          "has_more",
          "limit"
        ],
        "properties": {
          "object": {
            "const": "list"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Transfer"
            }
          },
          "next_cursor": {
            "type": "string",
            "nullable": true,
            "description": "Pass back as `cursor` for the next page."
          },
          "has_more": {
            "type": "boolean"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer",
            "nullable": true,
            "description": "Legacy offset/limit pagination."
          }
        }
      },
      "ApiKeyList": {
        "type": "object",
        "required": [
          "object",
          "data",
          "has_more",
          "limit"
        ],
        "properties": {
          "object": {
            "const": "list"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiKey"
            }
          },
          "next_cursor": {
            "type": "string",
            "nullable": true,
            "description": "Pass back as `cursor` for the next page."
          },
          "has_more": {
            "type": "boolean"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer",
            "nullable": true,
            "description": "Legacy offset/limit pagination."
          }
        }
      },
      "WebhookList": {
        "type": "object",
        "required": [
          "object",
          "data",
          "has_more",
          "limit"
        ],
        "properties": {
          "object": {
            "const": "list"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Webhook"
            }
          },
          "next_cursor": {
            "type": "string",
            "nullable": true,
            "description": "Pass back as `cursor` for the next page."
          },
          "has_more": {
            "type": "boolean"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer",
            "nullable": true,
            "description": "Legacy offset/limit pagination."
          }
        }
      },
      "AuditEntryList": {
        "type": "object",
        "required": [
          "object",
          "data",
          "has_more",
          "limit"
        ],
        "properties": {
          "object": {
            "const": "list"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AuditEntry"
            }
          },
          "next_cursor": {
            "type": "string",
            "nullable": true,
            "description": "Pass back as `cursor` for the next page."
          },
          "has_more": {
            "type": "boolean"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer",
            "nullable": true,
            "description": "Legacy offset/limit pagination."
          }
        }
      },
      "IntakeList": {
        "type": "object",
        "required": [
          "object",
          "data",
          "has_more",
          "limit"
        ],
        "properties": {
          "object": {
            "const": "list"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Intake"
            }
          },
          "next_cursor": {
            "type": "string",
            "nullable": true,
            "description": "Pass back as `cursor` for the next page."
          },
          "has_more": {
            "type": "boolean"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer",
            "nullable": true,
            "description": "Legacy offset/limit pagination."
          }
        }
      },
      "IntakeDocumentList": {
        "type": "object",
        "required": [
          "object",
          "data",
          "has_more",
          "limit"
        ],
        "properties": {
          "object": {
            "const": "list"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IntakeDocument"
            }
          },
          "next_cursor": {
            "type": "string",
            "nullable": true,
            "description": "Pass back as `cursor` for the next page."
          },
          "has_more": {
            "type": "boolean"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer",
            "nullable": true,
            "description": "Legacy offset/limit pagination."
          }
        }
      }
    },
    "parameters": {
      "limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 20
        },
        "description": "Page size. Maximum varies by endpoint (default 20, /v1/audit allows up to 500)."
      },
      "cursor": {
        "name": "cursor",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Opaque pagination cursor returned by a previous call as `next_cursor`."
      },
      "offset": {
        "name": "offset",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "default": 0
        },
        "description": "Legacy offset/limit pagination. Prefer `cursor` for stable paging under concurrent writes."
      }
    },
    "responses": {
      "401": {
        "description": "Unauthorized - missing, invalid, expired or revoked API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "403": {
        "description": "Forbidden - required scope not granted, or IP not allowed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "404": {
        "description": "Not found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "429": {
        "description": "Rate limit exceeded. See `X-RateLimit-*` and `Retry-After` headers.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "500": {
        "description": "Internal server error. Include `X-Request-Id` in support tickets.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "schema": {
          "type": "integer"
        },
        "description": "Per-minute quota for the workspace + endpoint class."
      },
      "X-RateLimit-Remaining": {
        "schema": {
          "type": "integer"
        },
        "description": "Requests left in the current 60s window."
      },
      "X-RateLimit-Reset": {
        "schema": {
          "type": "integer"
        },
        "description": "Epoch seconds when the current window resets."
      },
      "X-RateLimit-Policy": {
        "schema": {
          "type": "string"
        },
        "description": "RFC 8941-style policy string, e.g. `300;w=60`."
      },
      "Retry-After": {
        "schema": {
          "type": "integer"
        },
        "description": "Only on 429 - seconds until next acceptable request."
      },
      "X-Request-Id": {
        "schema": {
          "type": "string"
        }
      },
      "X-Coffrify-Api-Version": {
        "schema": {
          "type": "string",
          "example": "2026-05-14"
        }
      }
    }
  },
  "tags": [
    {
      "name": "Meta",
      "description": "Welcome + identity endpoints."
    },
    {
      "name": "Transfers",
      "description": "Create, list, inspect and revoke transfers."
    },
    {
      "name": "API keys",
      "description": "Manage your API keys (scopes, rotation, revocation)."
    },
    {
      "name": "Webhooks",
      "description": "Subscribe to events. 43+ event types in the catalog."
    },
    {
      "name": "Audit",
      "description": "Read the workspace audit log."
    },
    {
      "name": "Intake",
      "description": "Reception securisee: receive confidential documents end-to-end encrypted (the Stripe of secure intake). Manage intakes with a cof_ key (intake:read / intake:write); collect files from the browser with a publishable cip_ key."
    }
  ],
  "paths": {
    "/v1/welcome": {
      "get": {
        "tags": [
          "Meta"
        ],
        "summary": "Public welcome message + API metadata.",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "tags": [
          "Meta"
        ],
        "summary": "Resolve the API key into its workspace, scopes and user.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "$ref": "#/components/responses/401"
          }
        }
      }
    },
    "/v1/transfers": {
      "get": {
        "tags": [
          "Transfers"
        ],
        "summary": "List transfers in the workspace.",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "$ref": "#/components/parameters/offset"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "expired",
                "deleted",
                "scanning"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransferList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "429": {
            "$ref": "#/components/responses/429"
          }
        }
      },
      "post": {
        "tags": [
          "Transfers"
        ],
        "summary": "Create a transfer (returns a presigned upload token).",
        "responses": {
          "201": {
            "description": "Created"
          },
          "401": {
            "$ref": "#/components/responses/401"
          }
        }
      }
    },
    "/v1/transfers/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Transfers"
        ],
        "summary": "Fetch a transfer by ID.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "$ref": "#/components/responses/404"
          }
        }
      },
      "delete": {
        "tags": [
          "Transfers"
        ],
        "summary": "Revoke a transfer (delete files).",
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "$ref": "#/components/responses/404"
          }
        }
      }
    },
    "/v1/api-keys": {
      "get": {
        "tags": [
          "API keys"
        ],
        "summary": "List API keys for the workspace.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyList"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "API keys"
        ],
        "summary": "Create a new API key. Returns the raw key ONCE.",
        "responses": {
          "201": {
            "description": "Created"
          },
          "429": {
            "$ref": "#/components/responses/429"
          }
        }
      }
    },
    "/v1/api-keys/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "patch": {
        "tags": [
          "API keys"
        ],
        "summary": "Update name / IP allowlist / max_uses on a key.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "tags": [
          "API keys"
        ],
        "summary": "Revoke a key. Fires `api_key.revoked`.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/api-keys/{id}/rotate": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "tags": [
          "API keys"
        ],
        "summary": "Rotate a key - new value + grace period for the old one.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhooks.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookList"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Create a webhook. Returns the signing secret ONCE.",
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      },
      "patch": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Update a webhook.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete a webhook.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/webhooks/events": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List the event catalog (event types + payload shape).",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/webhooks/{id}/deliveries": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List delivery attempts for a webhook (debug).",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/audit": {
      "get": {
        "tags": [
          "Audit"
        ],
        "summary": "Query the workspace audit log.",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "$ref": "#/components/parameters/offset"
          },
          {
            "name": "action",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "actor_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "resource_type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuditEntryList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          }
        }
      }
    },
    "/v1/intakes": {
      "get": {
        "tags": [
          "Intake"
        ],
        "summary": "List intakes in the workspace.",
        "description": "Returns every active intake (kind=api_intake). Requires the `intake:read` scope.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IntakeList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          }
        }
      },
      "post": {
        "tags": [
          "Intake"
        ],
        "summary": "Create an intake.",
        "description": "Creates an intake and mints a publishable key (cip_), returned ONCE in `publishable_key`. The secret side is the cof_ key you call this with (scope `intake:write`). Activate E2E from the developer console before going live so documents are sealed to your key.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 80,
                    "description": "Human label, e.g. `KYC onboarding`."
                  },
                  "slug": {
                    "type": "string",
                    "description": "Optional URL-safe slug. Derived from `name` when omitted; a random suffix is added on collision."
                  },
                  "allowed_origins": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Browser origins allowed to use the publishable key. `*` allows any."
                  },
                  "reference_required": {
                    "type": "boolean",
                    "default": false
                  },
                  "metadata_policy": {
                    "type": "string",
                    "enum": [
                      "envelope",
                      "opaque"
                    ],
                    "default": "envelope"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. `publishable_key` is shown only here.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Intake"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "publishable_key": {
                          "type": "string",
                          "description": "Browser-safe cip_ key. Store it client-side; you cannot retrieve it again."
                        },
                        "warning": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error (missing/invalid name or slug).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/401"
          },
          "403": {
            "$ref": "#/components/responses/403"
          }
        }
      }
    },
    "/v1/intakes/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Intake"
        ],
        "summary": "Retrieve an intake.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Intake"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/404"
          }
        }
      },
      "patch": {
        "tags": [
          "Intake"
        ],
        "summary": "Update an intake.",
        "description": "Update `name`, `enabled`, `allowed_origins`, `metadata_policy` or `reference_required`. Requires `intake:write`.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "enabled": {
                    "type": "boolean"
                  },
                  "allowed_origins": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "metadata_policy": {
                    "type": "string",
                    "enum": [
                      "envelope",
                      "opaque"
                    ]
                  },
                  "reference_required": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Intake"
                }
              }
            }
          },
          "400": {
            "description": "No updatable fields provided.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/404"
          }
        }
      },
      "delete": {
        "tags": [
          "Intake"
        ],
        "summary": "Archive an intake.",
        "description": "Soft-archives the intake and revokes all its integration keys. Already-received documents stay decryptable. Requires `intake:write`.",
        "responses": {
          "200": {
            "description": "Archived.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "intake"
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "deleted": {
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/404"
          }
        }
      }
    },
    "/v1/intakes/{id}/documents": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Intake"
        ],
        "summary": "List documents received by an intake.",
        "description": "Metadata only: the content is E2E and sealed to your key. Filter by `reference` to fetch everything from one opaque dossier without Coffrify ever learning the real identity behind it. Requires `intake:read`.",
        "parameters": [
          {
            "name": "reference",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Exact match on `client_reference`."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by document status (e.g. `received`)."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "$ref": "#/components/parameters/cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IntakeDocumentList"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/404"
          }
        }
      }
    },
    "/v1/intakes/{id}/documents/{docId}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        },
        {
          "name": "docId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Intake"
        ],
        "summary": "Retrieve one received document (with download URLs).",
        "description": "Returns the document metadata, the E2E envelope (`wrapped_symmetric_key` + per-file IVs) and short-lived presigned GET URLs for the ciphertext blobs. Unwrap the symmetric key with your private key, then decrypt each blob (Coffrify `COFFRIFY` chunked format). Requires `intake:read`.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/IntakeDocument"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "country": {
                          "type": "string",
                          "nullable": true
                        },
                        "encryption": {
                          "type": "object",
                          "nullable": true,
                          "description": "E2E envelope, opaque to Coffrify. Null when the intake is not E2E.",
                          "properties": {
                            "mode": {
                              "const": "e2e_v1_asym"
                            },
                            "wrapped_symmetric_key": {
                              "type": "string",
                              "nullable": true,
                              "description": "AES key sealed to the intake X25519 public key (libsodium crypto_box_seal, base64url)."
                            },
                            "files": {
                              "type": "object",
                              "additionalProperties": true,
                              "description": "Per-file metadata keyed by name: iv_prefix, original_size, original_mime."
                            },
                            "chunk_size": {
                              "type": "integer",
                              "nullable": true
                            }
                          }
                        },
                        "files": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              },
                              "size_bytes": {
                                "type": "integer"
                              },
                              "display_mime": {
                                "type": "string",
                                "nullable": true
                              },
                              "download_url": {
                                "type": "string",
                                "nullable": true,
                                "description": "Presigned GET URL for the ciphertext blob."
                              },
                              "download_expires_in": {
                                "type": "integer",
                                "description": "Seconds until the URL expires (300)."
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/404"
          }
        }
      }
    },
    "/api/public/intake/config": {
      "servers": [
        {
          "url": "https://app.coffrify.com",
          "description": "Public SDK host"
        }
      ],
      "get": {
        "tags": [
          "Intake"
        ],
        "summary": "Resolve an intake config for the browser SDK.",
        "description": "Front-end endpoint used by coffrify-intake.js. Authenticated with a publishable key (`Authorization: Bearer cip_…` or `?key=cip_…`) and gated by the request Origin. Returns the intake E2E public key + policy so the SDK can seal the upload. No content ever transits here. CORS-enabled.",
        "security": [
          {
            "publishableKey": []
          }
        ],
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Publishable key, as an alternative to the Authorization header."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "intake_config"
                    },
                    "intake_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "e2e": {
                      "type": "object",
                      "properties": {
                        "enabled": {
                          "type": "boolean"
                        },
                        "public_key": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    },
                    "reference_required": {
                      "type": "boolean"
                    },
                    "metadata_policy": {
                      "type": "string",
                      "enum": [
                        "envelope",
                        "opaque"
                      ]
                    },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "max_files_per_submission": {
                          "type": "integer"
                        },
                        "max_bytes_per_submission": {
                          "type": "integer"
                        }
                      }
                    },
                    "chunk_size": {
                      "type": "integer",
                      "description": "AES-GCM chunk size in bytes (4 MiB)."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid publishable key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Origin not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/public/intake/submit": {
      "servers": [
        {
          "url": "https://app.coffrify.com",
          "description": "Public SDK host"
        }
      ],
      "post": {
        "tags": [
          "Intake"
        ],
        "summary": "Register a submission and get presigned upload URLs.",
        "description": "Front-end endpoint used by coffrify-intake.js. The browser posts file metadata (already encrypted client-side when E2E, sealed to the intake key) and receives presigned PUT URLs. Coffrify stores ciphertext only and fires a `deposit.created` webhook to the integrator. Authenticated with a publishable key + Origin. CORS-enabled.",
        "security": [
          {
            "publishableKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "files"
                ],
                "properties": {
                  "files": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "required": [
                        "name",
                        "size"
                      ],
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "size": {
                          "type": "integer"
                        },
                        "display_type": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    }
                  },
                  "client_reference": {
                    "type": "string",
                    "maxLength": 200,
                    "nullable": true
                  },
                  "envelope_metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Cleartext key/value metadata (max 30 keys). Never put secrets here; secret-looking keys are rejected."
                  },
                  "requester_email": {
                    "type": "string",
                    "nullable": true
                  },
                  "encryption_mode": {
                    "type": "string",
                    "enum": [
                      "server_side",
                      "e2e_v1_asym"
                    ],
                    "description": "Must be `e2e_v1_asym` when the intake enforces E2E."
                  },
                  "encryption_metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "nullable": true,
                    "description": "Required for E2E: { version, chunk_size, wrapped_symmetric_key, files }."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Submission registered. Upload each blob to its presigned URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "object": {
                      "const": "intake_submission"
                    },
                    "document_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "submission_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "client_reference": {
                      "type": "string",
                      "nullable": true
                    },
                    "uploads": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string",
                            "description": "Presigned PUT URL."
                          },
                          "content_type": {
                            "type": "string"
                          },
                          "storage_path": {
                            "type": "string"
                          },
                          "required_headers": {
                            "type": "object",
                            "additionalProperties": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error (no files, reference required, blocked extension, E2E mismatch).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid publishable key."
          },
          "402": {
            "description": "Uploads blocked (monthly cap reached or payment past due)."
          },
          "403": {
            "description": "Origin not allowed."
          },
          "413": {
            "description": "Submission too large."
          },
          "415": {
            "description": "Blocked file extension (executable)."
          },
          "422": {
            "description": "Invalid envelope metadata."
          }
        }
      }
    }
  },
  "x-coffrify": {
    "openapi_version": "1.0.0",
    "api_version": "2026-05-14",
    "generated_at": "2026-11-05T20:30:11.453Z",
    "rate_limit_doc": "https://docs.coffrify.com/api/rate-limits",
    "idempotency_doc": "https://docs.coffrify.com/api/idempotency",
    "event_catalog_endpoint": "/v1/webhooks/events",
    "webhooks": {
      "signing": {
        "standard_compliant": true,
        "spec": "https://www.standardwebhooks.com/",
        "headers_emitted": [
          "webhook-id (uuid - stable across retries; use for idempotency)",
          "webhook-timestamp (unix seconds)",
          "webhook-signature (`v1,<base64-hmac-sha256>` - multi-sig space-separated during rotation)",
          "X-Coffrify-Signature (legacy `t=<ts>,v1=<hex>` format - kept for back-compat)",
          "X-Coffrify-Event-Id, X-Coffrify-Event-Type (legacy headers)",
          "X-Coffrify-Test-Delivery: true (only on /v1/webhooks/{id}/test deliveries)"
        ],
        "idempotency": "Receivers should dedupe on `webhook-id`. The same id is used across all retries AND replays."
      },
      "replay": {
        "endpoint": "POST /v1/webhooks/{id}/test (action=replay via dashboard) - preserves the original event_id.",
        "marker": "Replay deliveries set `original_delivery_id` in the database and ship the same `webhook-id` header."
      },
      "rotation": {
        "endpoint": "POST /v1/webhooks/{id}/rotate-secret",
        "grace_window_default_hours": 24,
        "grace_window_max_hours": 168,
        "behavior": "Both the new and previous secrets validate during the grace window. Plan your rollover; after the window closes, only the new secret works."
      },
      "retry_policy": {
        "default_max_attempts": 10,
        "default_disable_after_consecutive_failures": 50,
        "override": "Set `retry_policy.disable_after_consecutive_failures` per webhook (1–10000).",
        "backoff_seconds": [
          60,
          300,
          1800,
          7200,
          21600,
          43200,
          86400,
          86400,
          86400
        ]
      },
      "sandbox": {
        "endpoint": "POST /v1/webhooks/{id}/test",
        "accepts": {
          "id": "uuid (required)",
          "event_type": "any catalog entry or 'ping' (default)",
          "data": "optional custom payload"
        }
      }
    }
  }
}