{
  "openapi": "3.1.0",
  "info": {
    "title": "Dermoptera Handoff API",
    "version": "1.0.0-beta.1",
    "summary": "One-time encrypted cross-device web-app state transfer.",
    "description": "The browser encrypts and validates state. The API stores ciphertext temporarily, enforces expiry, Project controls and one successful claim. Publishable Project keys are public identifiers, not secrets.",
    "license": { "name": "MIT", "identifier": "MIT" }
  },
  "servers": [{ "url": "https://api.dermoptera.work", "description": "Production candidate; not live until public release" }],
  "paths": {
    "/healthz": { "get": { "operationId": "getHealth", "responses": { "200": { "description": "Healthy" } } } },
    "/v1/projects/validate": { "post": { "operationId": "validateProject", "security": [{ "publishableKey": [] }], "responses": { "200": { "description": "Project is active" }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" } } } },
    "/v1/transfers": { "post": { "operationId": "createTransfer", "security": [{ "publishableKey": [] }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateTransfer" } } } }, "responses": { "201": { "description": "Encrypted transfer stored", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateResult" } } } }, "400": { "$ref": "#/components/responses/Error" }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "413": { "$ref": "#/components/responses/Error" }, "429": { "$ref": "#/components/responses/Error" } } } },
    "/v1/transfers/claim": { "post": { "operationId": "claimTransfer", "security": [{ "publishableKey": [] }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClaimTransfer" } } } }, "responses": { "200": { "description": "Claimed exactly once", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClaimResult" } } } }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "404": { "$ref": "#/components/responses/Error" }, "409": { "$ref": "#/components/responses/Error" }, "410": { "$ref": "#/components/responses/Error" }, "429": { "$ref": "#/components/responses/Error" } } } },
    "/v1/events": { "post": { "operationId": "recordClientEvent", "security": [{ "publishableKey": [] }], "description": "Best-effort aggregate diagnostics. Never include state, continuation URLs, tokens, keys, user identifiers or free text.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": false, "required": ["name"], "properties": { "name": { "type": "string", "enum": ["resume", "error"] } } } } } }, "responses": { "200": { "description": "Aggregate counter accepted" }, "400": { "$ref": "#/components/responses/Error" }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "429": { "$ref": "#/components/responses/Error" } } } },
    "/developer/v1/projects": { "get": { "operationId": "listProjects", "security": [{ "developerSession": [] }], "responses": { "200": { "description": "Projects" }, "401": { "$ref": "#/components/responses/Error" } } }, "post": { "operationId": "createProject", "description": "Create a Project. Free Beta accounts are limited to three Projects.", "security": [{ "developerSession": [] }], "responses": { "201": { "description": "Project and one-time-displayed publishable key" }, "401": { "$ref": "#/components/responses/Error" }, "429": { "$ref": "#/components/responses/Error" } } } },
    "/developer/v1/projects/{projectId}/origins": { "put": { "operationId": "replaceAllowedOrigins", "security": [{ "developerSession": [] }], "parameters": [{ "$ref": "#/components/parameters/ProjectId" }], "responses": { "200": { "description": "Origins replaced" }, "400": { "$ref": "#/components/responses/Error" }, "404": { "$ref": "#/components/responses/Error" } } } },
    "/developer/v1/projects/{projectId}/usage": { "get": { "operationId": "getUsage", "security": [{ "developerSession": [] }], "parameters": [{ "$ref": "#/components/parameters/ProjectId" }], "responses": { "200": { "description": "UTC month usage and quota" }, "404": { "$ref": "#/components/responses/Error" } } } },
    "/developer/v1/projects/{projectId}/rotate": { "post": { "operationId": "rotatePublishableKey", "security": [{ "developerSession": [] }], "parameters": [{ "$ref": "#/components/parameters/ProjectId" }], "responses": { "200": { "description": "Old key immediately revoked" }, "404": { "$ref": "#/components/responses/Error" } } } },
    "/developer/v1/projects/{projectId}/disable": { "post": { "operationId": "disableProject", "security": [{ "developerSession": [] }], "parameters": [{ "$ref": "#/components/parameters/ProjectId" }], "responses": { "200": { "description": "Disabled" }, "404": { "$ref": "#/components/responses/Error" } } } },
    "/developer/v1/projects/{projectId}": { "delete": { "operationId": "deleteProject", "security": [{ "developerSession": [] }], "parameters": [{ "$ref": "#/components/parameters/ProjectId" }], "responses": { "204": { "description": "Project, credentials, origins, usage, and unclaimed transfers deleted" }, "404": { "$ref": "#/components/responses/Error" } } } },
    "/developer/v1/account": { "delete": { "operationId": "deleteAccount", "security": [{ "developerSession": [] }], "responses": { "204": { "description": "Account and owned data deleted" }, "401": { "$ref": "#/components/responses/Error" } } } }
  },
  "components": {
    "securitySchemes": { "publishableKey": { "type": "apiKey", "in": "header", "name": "X-Publishable-Key", "description": "Public project identifier; not a secret. Origin allowlisting is a browser control, not client authentication, because non-browser clients can forge Origin. Quota, per-IP and per-Project limits also apply." }, "developerSession": { "type": "http", "scheme": "bearer" } },
    "parameters": { "ProjectId": { "name": "projectId", "in": "path", "required": true, "schema": { "type": "string" } } },
    "responses": { "Error": { "description": "Stable machine-readable error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } },
    "schemas": {
      "CreateTransfer": { "type": "object", "additionalProperties": false, "required": ["lookup","tokenHash","iv","ciphertext","ttl"], "properties": { "lookup": { "type": "string", "pattern": "^[A-Za-z0-9_-]{22}$", "description": "Unpadded base64url 128-bit lookup; the service stores its SHA-256 digest." }, "tokenHash": { "type": "string", "pattern": "^[A-Za-z0-9_-]{43}$", "description": "Unpadded base64url SHA-256 digest of the independent claim token." }, "iv": { "type": "string", "pattern": "^[A-Za-z0-9_-]{16}$", "description": "Unpadded base64url 96-bit AES-GCM IV." }, "ciphertext": { "type": "string", "description": "Unpadded base64url AES-256-GCM ciphertext including authentication tag." }, "ttl": { "type": "integer", "minimum": 60, "maximum": 1800, "default": 600 } }, "examples": [{ "lookup": "AAAAAAAAAAAAAAAAAAAAAA", "tokenHash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "iv": "AAAAAAAAAAAAAAAA", "ciphertext": "example_ciphertext_is_generated_by_the_sdk", "ttl": 600 }] },
      "ClaimTransfer": { "type": "object", "additionalProperties": false, "required": ["lookup","token"], "properties": { "lookup": { "type": "string", "pattern": "^[A-Za-z0-9_-]{22}$" }, "token": { "type": "string", "pattern": "^[A-Za-z0-9_-]{43}$", "description": "Raw claim token from the URL fragment. Never log it." } } },
      "CreateResult": { "type": "object", "additionalProperties": false, "required": ["expiresAt"], "properties": { "expiresAt": { "type": "integer", "description": "Unix time in seconds." } }, "examples": [{ "expiresAt": 1790673000 }] },
      "ClaimResult": { "type": "object", "additionalProperties": false, "required": ["ciphertext","iv","expiresAt"], "properties": { "ciphertext": { "type": "string" }, "iv": { "type": "string" }, "expiresAt": { "type": "integer", "description": "Original expiry as Unix time in seconds." } } },
      "ErrorResponse": { "type": "object", "required": ["error"], "properties": { "error": { "type": "object", "required": ["code","message","retryable","docs"], "properties": { "code": { "type": "string", "enum": ["INVALID_REQUEST","INVALID_CREDENTIAL","ORIGIN_NOT_ALLOWED","PROJECT_DISABLED","PAYLOAD_TOO_LARGE","RATE_LIMITED","QUOTA_EXCEEDED","EXPIRED","ALREADY_CLAIMED","INVALID_TOKEN","UNAUTHORIZED","FORBIDDEN","NOT_FOUND","SERVICE_UNAVAILABLE","INTERNAL_ERROR"] }, "message": { "type": "string" }, "retryable": { "type": "boolean" }, "docs": { "type": "string" } } } } }
    }
  }
}
