{
  "openapi": "3.0.0",
  "info": {
    "title": "Proof of Human API",
    "description": "The Proof of Human API verifies credentialed audio by itself or a legacy audio + `.poh` pair.\nA valid result confirms PoH's signature and binds its account of the editing PoH witnessed to\nthe exact export. The evidence grade is **report-only**; your screening policy stays yours.\n\n## Authentication surfaces\n\nThe published partner API uses `x-api-key`; keep that credential server-side and never ship it\nin a browser or app bundle. Every non-public operation in this partner contract—including\nproof-id reads—requires that key. The PoH macOS app uses a separate App API and authorization\nboundary that is deliberately outside the published partner contract. Health is public.\n\n## Quickstart\n\n```bash\nnpm install @proof-of-human/ts-sdk\n```\n\n```ts\nimport { PoHClient } from \"@proof-of-human/ts-sdk\";\nconst poh = new PoHClient({ apiKey: process.env.POH_API_KEY! });\n\nconst upload = await poh.createUpload();        // presigned slot — POST the audio to it\nconst result = await poh.verify({ uploadId: upload.uploadId }); // credentialed audio\n// Legacy sidecar: poh.verify({ uploadId: upload.uploadId, poh: pohBase64 })\nresult.verdict;   // \"valid\" when the proof is PoH's AND matches the audio\nresult.grade;     // report-only { human, ai, unknown, riskCategory, rubricVersion }\n```\n\n## What a proof attests\n\nA `valid` proof attests **PoH's signature** and the **binding to the exact audio file** — a\nrecorded process account, not an \"AI-free\" certification.\n\n## Idempotency & retries\n\nEvery `GET` is safe to retry. `DELETE /proofs/{proofId}` is idempotent and reports its outcome —\na repeat returns `already_erased`. `POST /verify` has no side effects. `POST /uploads` mints a\nfresh short-lived slot each call, so retrying a failed call is safe (unused slots expire on\ntheir own). `POST /partner/keys/rotate` requires an `Idempotency-Key` header: retrying with the\nsame key within 24 hours replays the original result — same key, same secret — instead of\nminting again (the TypeScript SDK sends one automatically). Proof issuance keeps that header\noptional for the shipped app; the server binds retries to the complete request and returns 409\nif the same replay identity is reused with changed request data.\n\n## Errors\n\nEvery error response is `{ \"error\": string, \"code\": string }`. `error` is a human-readable\nmessage; `code` is a stable slug to branch on (`invalid_request`, `forbidden`, `not_found`,\n`conflict`, `unprocessable`, `rate_limited`, `internal`, plus a few endpoint-specific codes).\nThe same shape is returned whether a handler or the API gateway rejected the call. Treat an\nunrecognized `code` as its HTTP status class.\n\n## Request IDs\n\nEvery response carries an `X-Request-Id` header — a stable id for that exact call, shared with\nour logs (it also appears as the AWS `x-amzn-requestid`). Quote it when reporting a problem so we\ncan trace the request end to end. The TypeScript SDK surfaces it on failures as\n`PoHError.requestId`.\n\n## Timestamps\n\nEvery timestamp this API returns is **unix epoch seconds** — `createdAt`, `issuedAt`,\n`deployedAt`, and the `X-RateLimit-Reset` header alike. A list or verify result's `issuedAt` is\nthe same value as that proof's canonical `ProofDocument.createdAt`.\n\n## Rate limits\n\nEvery key is rate-limited and metered by its plan; `GET /partner/usage` shows your limits and\nper-day consumption. Partner self-service responses under `/partner` carry\n`X-RateLimit-Limit` (your monthly quota) and `X-RateLimit-Reset` (unix seconds until the quota\nwindow resets); `GET /partner/usage` also\nreturns `X-RateLimit-Remaining` (the live quota headroom — the authoritative source, since\nlimiting is metered at the edge). Exceeding a limit returns `429` with `Retry-After` and the\n`{ error, code: \"rate_limited\" }` body — back off and retry (the SDK does this automatically).\nKey rotation runs in its own deliberately low throttle lane.\n\n## Integration monitoring\n\nPoll `GET /health` for public reachability and `GET /partner/usage` for the same quota counters\nused by the gateway. In your client, record HTTP status, latency, `X-Request-Id`, and the\n`X-RateLimit-*` / `Retry-After` headers — never the API key, uploaded audio, or `.poh` body.\nAlert on sustained 5xx responses, latency regression, repeated 429s, and quota headroom below\nyour operating threshold. PoH separately monitors end-to-end availability, per-client outcomes,\nverification verdict distribution, C2PA embedding failures, latency, and background-job health.\n\n## Use with AI agents\n\nPoint your coding agent (Cursor, Claude Code, Copilot, …) at the context files:\n[llms.txt](https://proofofhuman.fm/llms.txt) is a concise map of every endpoint, and\n[llms-full.txt](https://proofofhuman.fm/llms-full.txt) inlines each endpoint's parameters. Or use\nthe **Generate MCP** action in this reference to expose the API to an agent over MCP.\n\n## Model-first\n\nEvery route, type, example, and code sample on this page is generated from one TypeSpec model,\nso what the docs say, the API enforces.",
    "version": "2.4.2"
  },
  "tags": [
    {
      "name": "Verify",
      "description": "Verify one credentialed WAV by itself, or a legacy audio + `.poh` pair. The core partner flow."
    },
    {
      "name": "Proofs",
      "description": "Read, list, interpret, and erase issued proofs."
    },
    {
      "name": "API keys",
      "description": "Self-service key rotation, inventory, and usage — authenticated by your own key."
    },
    {
      "name": "System",
      "description": "Health, the download redirect, and content-free product telemetry."
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "operationId": "health",
        "description": "Public deployment health/version stamp. No API key or device signature is required.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Reusable 200 response with the standard request-correlation header.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          },
          "500": {
            "description": "Shaped internal failure. Gateway and Lambda failures use the same ApiError body.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "tags": [
          "System"
        ],
        "security": [
          {}
        ],
        "x-poh-auth-surface": "public",
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.proofofhuman.fm/v1/health\"   # { \"ok\": true, \"version\": \"…\", \"commit\": \"…\" }"
          }
        ]
      }
    },
    "/partner/keys": {
      "get": {
        "operationId": "listPartnerKeys",
        "description": "List the caller's API keys (metadata only — never secrets). The calling key identifies the\npartner; there is nothing to pass.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "page size; omit both limit and cursor to return the complete legacy inventory",
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 1,
              "maximum": 100
            },
            "explode": false
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "opaque `nextCursor` from the preceding response; activates bounded pagination",
            "schema": {
              "type": "string"
            },
            "explode": false
          }
        ],
        "responses": {
          "200": {
            "description": "Reusable partner 200 response with request-correlation and rate-limit headers.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Reset": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerKeysResponse"
                }
              }
            }
          },
          "400": {
            "description": "The server could not understand the request due to invalid syntax.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The caller's API key is missing/invalid for this action, or the device is not enrolled / revoked.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The caller exceeded its rate limit or quota — wait `Retry-After` seconds and retry.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Reset": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "required": false,
                "description": "seconds to wait before retrying",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Shaped internal failure. Gateway and Lambda failures use the same ApiError body.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "tags": [
          "API keys"
        ],
        "x-poh-auth-surface": "partner-api-key",
        "x-codeSamples": [
          {
            "lang": "TypeScript",
            "label": "SDK",
            "source": "const { keys } = await poh.partner.listKeys();\nfor (const k of keys) console.log(k.keyId, k.status, k.name); // never a secret"
          },
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.proofofhuman.fm/v1/partner/keys\" -H \"x-api-key: $POH_API_KEY\""
          }
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ]
      }
    },
    "/partner/keys/rotate": {
      "post": {
        "operationId": "rotatePartnerKey",
        "description": "Rotate the caller's API key: mints a new key on the same plan and returns its secret — the only\ntime it is ever visible. The key that made this call keeps working for a grace window\n(`previousKeyExpiresAt`, 72h) so integrations cut over with zero downtime, then expires\nautomatically. Rotations are audit-logged.\n\nSend an `Idempotency-Key` header (the SDK does automatically): if the response is lost to a\nnetwork failure, retrying with the same key within 24 hours replays the original result — same\nkey id, same secret — instead of minting a second key. Without it, a retry after a completed\nrotation is refused (409), because the caller's key is already in its grace window.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "a unique high-entropy string — a UUID is ideal (16–128 chars); reuse it to safely retry this exact rotation for 24h",
            "schema": {
              "type": "string",
              "minLength": 16,
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "201": {
            "description": "The request has succeeded and a new resource has been created as a result.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Reset": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "idempotency-replayed": {
                "required": false,
                "description": "`true` when the original rotation response was replayed",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RotatedKeyResponse"
                },
                "examples": {
                  "Rotate with a zero-downtime cutover": {
                    "summary": "Rotate with a zero-downtime cutover",
                    "description": "The new secret is returned exactly once; the old key keeps working until `previousKeyExpiresAt`. Retrying with the same Idempotency-Key within 24h replays this exact result.",
                    "value": {
                      "apiKey": "poh_live_<returned-once>",
                      "keyId": "7b2mk9p4za",
                      "name": "acme-2026-07-07",
                      "previousKeyExpiresAt": 1783719200
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The server could not understand the request due to invalid syntax.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The caller's API key is missing/invalid for this action, or the device is not enrolled / revoked.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "The request conflicts with the current state of the server.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The caller exceeded its rate limit or quota — wait `Retry-After` seconds and retry.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Reset": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "required": false,
                "description": "seconds to wait before retrying",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Shaped internal failure. Gateway and Lambda failures use the same ApiError body.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "tags": [
          "API keys"
        ],
        "x-poh-auth-surface": "partner-api-key",
        "x-codeSamples": [
          {
            "lang": "TypeScript",
            "label": "SDK",
            "source": "// mint a new key; the old one keeps working for 72h so you can cut over with no downtime.\n// The SDK sends an Idempotency-Key automatically, so a retried call can never double-mint.\nconst rotated = await poh.partner.rotateKey();\nconsole.log(rotated.apiKey); // the new secret — shown ONCE, store it now\n// old key stops working at rotated.previousKeyExpiresAt (unix seconds)"
          },
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://api.proofofhuman.fm/v1/partner/keys/rotate\" \\\n  -H \"x-api-key: $POH_API_KEY\" \\\n  -H \"Idempotency-Key: $(uuidgen)\"   # retry with the SAME value to replay, never double-mint"
          }
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ]
      }
    },
    "/partner/usage": {
      "get": {
        "operationId": "getPartnerUsage",
        "description": "The caller's request usage against its quota, per UTC day — the observability half of key\nself-service. Data comes from the metering plan itself, so it matches exactly what billing\nand throttling see.",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "how many days back to include (default 30)",
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 1,
              "maximum": 90
            },
            "explode": false
          }
        ],
        "responses": {
          "200": {
            "description": "`GET /partner/usage` success — carries the full rate-limit header trio.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Reset": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerUsageResponse"
                }
              }
            }
          },
          "400": {
            "description": "The server could not understand the request due to invalid syntax.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The caller's API key is missing/invalid for this action, or the device is not enrolled / revoked.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The caller exceeded its rate limit or quota — wait `Retry-After` seconds and retry.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Reset": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "required": false,
                "description": "seconds to wait before retrying",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Shaped internal failure. Gateway and Lambda failures use the same ApiError body.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "tags": [
          "API keys"
        ],
        "x-poh-auth-surface": "partner-api-key",
        "x-codeSamples": [
          {
            "lang": "TypeScript",
            "label": "SDK",
            "source": "const usage = await poh.partner.usage({ days: 30 });\nconsole.log(usage.plan.quotaLimit, usage.plan.quotaPeriod);\nfor (const d of usage.days) console.log(d.date, d.used, d.remaining);"
          },
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.proofofhuman.fm/v1/partner/usage?days=30\" -H \"x-api-key: $POH_API_KEY\""
          }
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ]
      }
    },
    "/proofs": {
      "get": {
        "operationId": "listProofs",
        "description": "List the caller's issued proofs, newest first — the restore path for a reinstalled app. The\nenrolled device key IS the library identity: a machine that still holds its key can rebuild its\nproof library with no local data. Tenant-scoped; pending (incomplete) proofs are excluded.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 1,
              "maximum": 100
            },
            "explode": false
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "explode": false
          }
        ],
        "responses": {
          "200": {
            "description": "Reusable 200 response with the standard request-correlation header.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListProofsResponse"
                },
                "examples": {
                  "First page of the caller's library": {
                    "summary": "First page of the caller's library",
                    "description": "Pass `nextCursor` back as `?cursor=` for the next page; it is absent on the last page.",
                    "value": {
                      "items": [
                        {
                          "proofId": "87c16563-a9d9-4bb8-81c7-62b83fce841b",
                          "song": "Midnight Take",
                          "issuedAt": 1783460000,
                          "classification": "human",
                          "hasFeedback": true
                        }
                      ],
                      "nextCursor": "eyJwayI6IlBST09GIzg3YzE2NTYzIn0="
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The server could not understand the request due to invalid syntax.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The caller's API key is missing/invalid for this action, or the device is not enrolled / revoked.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The caller exceeded its rate limit or quota — wait `Retry-After` seconds and retry.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Reset": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "required": false,
                "description": "seconds to wait before retrying",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Shaped internal failure. Gateway and Lambda failures use the same ApiError body.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "tags": [
          "Proofs"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "x-poh-auth-surface": "partner-api-key",
        "x-codeSamples": [
          {
            "lang": "TypeScript",
            "label": "SDK",
            "source": "const page = await poh.listProofs({ limit: 50 });\nfor (const p of page.items) console.log(p.proofId, p.song, p.classification);\nif (page.nextCursor) {\n  const next = await poh.listProofs({ cursor: page.nextCursor });\n}"
          },
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.proofofhuman.fm/v1/proofs?limit=50\" -H \"x-api-key: $POH_API_KEY\""
          }
        ]
      }
    },
    "/proofs/{proofId}": {
      "get": {
        "operationId": "getProof",
        "description": "Fetch one proof's restorable artifact: the stored proof record plus its sealed `.poh`. Returns\nONLY the shareable proof file and display fields — never the retained audio or behavioral trace.\nTenant-scoped: another tenant's proofId reads as 404.",
        "parameters": [
          {
            "name": "proofId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reusable 200 response with the standard request-correlation header.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetProofResponse"
                }
              }
            }
          },
          "403": {
            "description": "The caller's API key is missing/invalid for this action, or the device is not enrolled / revoked.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "The server cannot find the requested resource.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The caller exceeded its rate limit or quota — wait `Retry-After` seconds and retry.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Reset": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "required": false,
                "description": "seconds to wait before retrying",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Shaped internal failure. Gateway and Lambda failures use the same ApiError body.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "tags": [
          "Proofs"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "x-poh-auth-surface": "partner-api-key",
        "x-codeSamples": [
          {
            "lang": "TypeScript",
            "label": "SDK",
            "source": "const proof = await poh.getProof(\"87c16563-a9d9-4bb8-81c7-62b83fce841b\");\nconsole.log(proof.song, proof.classification);\n// proof.poh is the base64 .poh — identical to what issuance produced"
          },
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.proofofhuman.fm/v1/proofs/87c16563-a9d9-4bb8-81c7-62b83fce841b\" -H \"x-api-key: $POH_API_KEY\""
          }
        ]
      },
      "delete": {
        "operationId": "eraseProof",
        "description": "Erase a proof's retained data and record (GDPR/CCPA right-to-erasure) and return a receipt.\nIdempotent and safe to retry blindly: the call that removes the proof reports `erased`; a repeat\n— or a proofId with nothing behind it — reports `already_erased` with the same 200. Tenant-scoped:\na caller can only ever erase data held under its own API key, and erasure always converges on\n\"gone\".",
        "parameters": [
          {
            "name": "proofId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reusable 200 response with the standard request-correlation header.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EraseProofResponse"
                }
              }
            }
          },
          "403": {
            "description": "The caller's API key is missing/invalid for this action, or the device is not enrolled / revoked.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The caller exceeded its rate limit or quota — wait `Retry-After` seconds and retry.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Reset": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "required": false,
                "description": "seconds to wait before retrying",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Shaped internal failure. Gateway and Lambda failures use the same ApiError body.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "tags": [
          "Proofs"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "x-poh-auth-surface": "partner-api-key",
        "x-codeSamples": [
          {
            "lang": "TypeScript",
            "label": "SDK",
            "source": "const receipt = await poh.eraseProof(\"87c16563-a9d9-4bb8-81c7-62b83fce841b\");\nconsole.log(receipt.outcome); // \"erased\" — or \"already_erased\" on a repeat (idempotent)"
          },
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X DELETE \"https://api.proofofhuman.fm/v1/proofs/$PROOF_ID\" -H \"x-api-key: $POH_API_KEY\"\n# → { \"proofId\": \"…\", \"outcome\": \"erased\", \"erasedAt\": 1783460000 }"
          }
        ]
      }
    },
    "/proofs/{proofId}/asset": {
      "get": {
        "operationId": "getEmbeddedAsset",
        "description": "Fetch a short-lived download URL for this proof's one-file C2PA audio artifact.",
        "parameters": [
          {
            "name": "proofId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reusable 200 response with the standard request-correlation header.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmbeddedAssetResponse"
                }
              }
            }
          },
          "403": {
            "description": "The caller's API key is missing/invalid for this action, or the device is not enrolled / revoked.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "The server cannot find the requested resource.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The caller exceeded its rate limit or quota — wait `Retry-After` seconds and retry.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Reset": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "required": false,
                "description": "seconds to wait before retrying",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Shaped internal failure. Gateway and Lambda failures use the same ApiError body.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "tags": [
          "Proofs"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "x-poh-auth-surface": "partner-api-key"
      }
    },
    "/proofs/{proofId}/interpretation": {
      "get": {
        "operationId": "getInterpretation",
        "description": "Fetch the async evidence-selected process note for a proof, by id — the poll endpoint the\nverifier UI hits while a summary is still generating. Returns `pending` (poll again shortly),\n`ready` with the summary, or `unavailable`. Keyed by the unguessable proofId and report-only.\nV1 policy decision: proofId is the lookup capability, but it is NOT sufficient authentication\non its own. The body is tenant-data-free, yet the deployed gateway still requires a partner API\nkey or enrolled-device signature; this preserves the current anti-abuse boundary without\nwidening access. 404 only when no proof with that id exists.",
        "parameters": [
          {
            "name": "proofId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reusable 200 response with the standard request-correlation header.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Interpretation"
                }
              }
            }
          },
          "403": {
            "description": "The caller's API key is missing/invalid for this action, or the device is not enrolled / revoked.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "The server cannot find the requested resource.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The caller exceeded its rate limit or quota — wait `Retry-After` seconds and retry.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Reset": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "required": false,
                "description": "seconds to wait before retrying",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Shaped internal failure. Gateway and Lambda failures use the same ApiError body.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "tags": [
          "Proofs"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "x-poh-auth-surface": "partner-api-key",
        "x-codeSamples": [
          {
            "lang": "TypeScript",
            "label": "SDK",
            "source": "// Poll for at most two minutes; a pending result can be checked again later.\nconst deadline = Date.now() + 120_000;\nlet interp = await poh.getInterpretation(proofId);\nwhile (interp.status === \"pending\" && Date.now() < deadline) {\n  await new Promise((r) => setTimeout(r, (interp.retryAfterSeconds ?? 2) * 1000));\n  interp = await poh.getInterpretation(proofId);\n}\nif (interp.status === \"ready\") console.log(interp.text);"
          },
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.proofofhuman.fm/v1/proofs/$PROOF_ID/interpretation\" -H \"x-api-key: $POH_API_KEY\""
          }
        ]
      }
    },
    "/uploads": {
      "post": {
        "operationId": "createUpload",
        "description": "Get a short-lived, presigned upload URL for audio (for issuing or verifying a proof).",
        "parameters": [
          {
            "name": "purpose",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/UploadPurpose"
            },
            "explode": false
          }
        ],
        "responses": {
          "200": {
            "description": "Reusable 200 response with the standard request-correlation header.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadResponse"
                }
              }
            }
          },
          "403": {
            "description": "The caller's API key is missing/invalid for this action, or the device is not enrolled / revoked.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The caller exceeded its rate limit or quota — wait `Retry-After` seconds and retry.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Reset": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "required": false,
                "description": "seconds to wait before retrying",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Shaped internal failure. Gateway and Lambda failures use the same ApiError body.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "tags": [
          "Verify"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "x-poh-auth-surface": "partner-api-key",
        "x-codeSamples": [
          {
            "lang": "TypeScript",
            "label": "SDK",
            "source": "import { readFile } from \"node:fs/promises\";\nimport { PoHClient } from \"@proof-of-human/ts-sdk\";\nconst poh = new PoHClient({ apiKey: process.env.POH_API_KEY! });\n\n// 1. ask for a short-lived, presigned upload slot\nconst upload = await poh.createUpload();\n\n// 2. POST the audio bytes to it (multipart/form-data: every field, then the file)\nconst bytes = await readFile(\"song.wav\");\nconst form = new FormData();\nfor (const [k, v] of Object.entries(upload.fields)) form.append(k, v);\nform.append(\"file\", new Blob([new Uint8Array(bytes)], { type: \"audio/wav\" }), \"song.wav\");\nconst posted = await fetch(upload.url, { method: \"POST\", body: form });\nif (!posted.ok) throw new Error(`audio upload failed: ${posted.status}`);\n// upload.uploadId now points at the stored audio — pass it to verify()"
          },
          {
            "lang": "Shell",
            "label": "curl",
            "source": "UPLOAD=$(curl -fsS -X POST \"https://api.proofofhuman.fm/v1/uploads?purpose=verify\" -H \"x-api-key: $POH_API_KEY\")\nUPLOAD_ID=$(jq -r .uploadId <<<\"$UPLOAD\")\nUPLOAD_URL=$(jq -r .url <<<\"$UPLOAD\")\nFORM_FIELDS=()\nwhile IFS=$'\\t' read -r key value; do FORM_FIELDS+=( -F \"$key=$value\" ); done \\\n  < <(jq -r '.fields | to_entries[] | [.key, .value] | @tsv' <<<\"$UPLOAD\")\ncurl -fsS -X POST \"$UPLOAD_URL\" \"${FORM_FIELDS[@]}\" -F \"file=@song.wav;type=audio/wav\"\n# pass $UPLOAD_ID to POST /verify"
          }
        ]
      }
    },
    "/verify": {
      "post": {
        "operationId": "verifyProof",
        "description": "Verify either an uploaded one-file C2PA/PoH audio asset or a legacy uploaded audio + proof pair.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Reusable 200 response with the standard request-correlation header.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifyResponse"
                },
                "examples": {
                  "Verify a credentialed WAV with one file": {
                    "summary": "Verify a credentialed WAV with one file",
                    "description": "Post the WAV to the presigned slot from POST /uploads, then send only its uploadId. For a legacy sidecar, add the base64 `.poh` as `poh`. `valid` means the proof is PoH's and bound to exactly these bytes.",
                    "value": {
                      "good": true,
                      "verdict": "valid",
                      "proofId": "87c16563-a9d9-4bb8-81c7-62b83fce841b",
                      "song": "Midnight Take",
                      "classification": "human"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The server could not understand the request due to invalid syntax.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "403": {
            "description": "The caller's API key is missing/invalid for this action, or the device is not enrolled / revoked.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "The caller exceeded its rate limit or quota — wait `Retry-After` seconds and retry.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Reset": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "required": false,
                "description": "seconds to wait before retrying",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Shaped internal failure. Gateway and Lambda failures use the same ApiError body.",
            "headers": {
              "X-Request-Id": {
                "required": false,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "tags": [
          "Verify"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyRequest"
              },
              "examples": {
                "Verify a credentialed WAV with one file": {
                  "summary": "Verify a credentialed WAV with one file",
                  "description": "Post the WAV to the presigned slot from POST /uploads, then send only its uploadId. For a legacy sidecar, add the base64 `.poh` as `poh`. `valid` means the proof is PoH's and bound to exactly these bytes.",
                  "value": {
                    "uploadId": "9f3a2c7e-5b14-4d2a-8e6f-1c2b3a4d5e6f"
                  }
                }
              }
            }
          }
        },
        "x-poh-auth-surface": "partner-api-key",
        "x-codeSamples": [
          {
            "lang": "TypeScript",
            "label": "SDK",
            "source": "import { PoHClient } from \"@proof-of-human/ts-sdk\";\nconst poh = new PoHClient({ apiKey: process.env.POH_API_KEY! });\n\n// Credentialed WAV: one file, so no sidecar field.\nconst result = await poh.verify({ uploadId: upload.uploadId });\n\n// Legacy fallback: poh.verify({ uploadId: upload.uploadId, poh: pohFileBase64 });\n\nif (result.good && result.verdict === \"valid\") {\n  // signature checks out AND the proof is bound to exactly this audio\n  console.log(result.classification); // \"human\" | \"ai\" | \"unknown\" (report-only)\n  console.log(result.grade);          // { human, ai, unknown, riskCategory, rubricVersion }\n}"
          },
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://api.proofofhuman.fm/v1/verify\" \\\n  -H \"x-api-key: $POH_API_KEY\" \\\n  -H \"content-type: application/json\" \\\n  -d \"{\\\"uploadId\\\":\\\"$UPLOAD_ID\\\"}\"\n# Legacy fallback: include \"poh\":\"<base64 .poh>\" in the JSON body."
          }
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ]
      }
    }
  },
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "components": {
    "schemas": {
      "HealthResponse": {
        "type": "object",
        "required": [
          "ok",
          "version",
          "commit",
          "deployedAt"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "version": {
            "type": "string",
            "minLength": 1,
            "description": "deployed API/SDK semantic version; matches `info.version` in the production OpenAPI"
          },
          "commit": {
            "type": "string",
            "minLength": 1
          },
          "deployedAt": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UnixSeconds"
              }
            ],
            "description": "unix seconds of the last deploy (same unit as every other timestamp in this API)"
          }
        },
        "description": "Public, non-sensitive deployment stamp for smoke tests and source-to-deploy verification."
      },
      "ApiError": {
        "type": "object",
        "required": [
          "error",
          "code"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "violations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FieldViolation"
            },
            "description": "Machine-readable field failures for a 400 produced by operation request validation."
          }
        },
        "description": "A structured error. `error` is a human-readable message (may change); `code` is a STABLE,\nmachine-readable slug you can branch on. Common codes: `invalid_request` (400), `forbidden`\n(403), `not_found` (404), `conflict` (409), `unprocessable` (422), `rate_limited` (429),\n`internal` (5xx). A few endpoints return more specific codes — e.g. rotation returns\n`key_rotating`, `rotation_in_progress`, or `idempotency_key_reused`. New codes may be added over\ntime, so treat an unrecognized code as its HTTP status class."
      },
      "PartnerKeysResponse": {
        "type": "object",
        "required": [
          "partner",
          "keys"
        ],
        "properties": {
          "partner": {
            "type": "string",
            "description": "your partner account slug"
          },
          "keys": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerKey"
            },
            "description": "every key on this page (metadata only — never a secret), including expired and revoked keys"
          },
          "nextCursor": {
            "type": "string",
            "description": "opaque cursor for the next page; absent when this is the final page"
          }
        },
        "example": {
          "partner": "acme",
          "keys": [
            {
              "keyId": "3n7kp2q9xa",
              "name": "acme-2026-07-07",
              "createdAt": 1783460000,
              "status": "active"
            }
          ]
        }
      },
      "RotatedKeyResponse": {
        "type": "object",
        "required": [
          "apiKey",
          "keyId",
          "name",
          "previousKeyExpiresAt"
        ],
        "properties": {
          "apiKey": {
            "type": "string",
            "description": "the NEW secret — store it now, it is never returned again"
          },
          "keyId": {
            "type": "string",
            "description": "the new key's id (safe to log)"
          },
          "name": {
            "type": "string",
            "description": "the new key's display name"
          },
          "previousKeyExpiresAt": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UnixSeconds"
              }
            ],
            "description": "unix seconds until which the previous key keeps working — the zero-downtime cutover window (72h)"
          }
        },
        "description": "The one-time result of a rotation. Store `apiKey` now — it is never retrievable again.",
        "example": {
          "apiKey": "poh_live_<returned-once>",
          "keyId": "7b2mk9p4za",
          "name": "acme-2026-07-07",
          "previousKeyExpiresAt": 1783719200
        }
      },
      "PartnerUsageResponse": {
        "type": "object",
        "required": [
          "partner",
          "plan",
          "days"
        ],
        "properties": {
          "partner": {
            "type": "string",
            "description": "your partner account slug"
          },
          "plan": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PartnerUsagePlan"
              }
            ],
            "description": "the plan's throttle + quota limits"
          },
          "days": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerDayUsage"
            },
            "description": "per-day request counts, oldest first, over the requested window"
          }
        },
        "example": {
          "partner": "acme",
          "plan": {
            "rateLimit": 10,
            "burstLimit": 20,
            "quotaLimit": 50000,
            "quotaPeriod": "MONTH"
          },
          "days": [
            {
              "date": "2026-07-06",
              "used": 1420,
              "remaining": 48580
            },
            {
              "date": "2026-07-07",
              "used": 1893,
              "remaining": 46687
            }
          ]
        }
      },
      "ListProofsResponse": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProofListItem"
            },
            "description": "the caller's proofs, newest first"
          },
          "nextCursor": {
            "type": "string",
            "description": "opaque page cursor; pass it back as `?cursor=` to fetch the next page. Absent on the last page"
          }
        },
        "example": {
          "items": [
            {
              "proofId": "87c16563-a9d9-4bb8-81c7-62b83fce841b",
              "song": "Midnight Take",
              "issuedAt": 1783460000,
              "classification": "human",
              "hasFeedback": true
            }
          ],
          "nextCursor": "eyJwayI6IlBST09GIzg3YzE2NTYzIn0="
        }
      },
      "GetProofResponse": {
        "type": "object",
        "required": [
          "proofId",
          "song",
          "issuedAt",
          "hasFeedback",
          "poh"
        ],
        "properties": {
          "proofId": {
            "type": "string",
            "minLength": 1
          },
          "song": {
            "type": "string",
            "minLength": 1
          },
          "issuedAt": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UnixSeconds"
              }
            ],
            "description": "unix seconds at issuance — the same value as this proof's canonical `ProofDocument.createdAt`"
          },
          "classification": {
            "$ref": "#/components/schemas/Classification"
          },
          "grade": {
            "allOf": [
              {
                "$ref": "#/components/schemas/GradeBreakdown"
              }
            ],
            "description": "absent on proofs issued before grading shipped"
          },
          "hasFeedback": {
            "type": "boolean",
            "description": "whether the post-export survey was completed — a restored library must not re-ask"
          },
          "embeddedCredential": {
            "allOf": [
              {
                "$ref": "#/components/schemas/EmbeddedCredential"
              }
            ],
            "description": "present when a self-contained C2PA audio artifact is available for this proof"
          },
          "poh": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Base64"
              }
            ],
            "description": "base64 of the sealed `.poh` — identical in content to what issuance returned"
          },
          "interpretation": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Interpretation"
              }
            ],
            "description": "the async evidence-selected process note, if one has been generated for this proof"
          }
        },
        "description": "A proof's restorable artifact: the library row plus the sealed `.poh` bytes."
      },
      "EraseProofResponse": {
        "type": "object",
        "required": [
          "proofId",
          "outcome",
          "erasedAt"
        ],
        "properties": {
          "proofId": {
            "type": "string",
            "minLength": 1,
            "description": "the proofId the erasure addressed"
          },
          "outcome": {
            "$ref": "#/components/schemas/EraseOutcome"
          },
          "erasedAt": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UnixSeconds"
              }
            ],
            "description": "unix seconds when this erasure pass completed"
          }
        },
        "description": "The receipt for a right-to-erasure call.",
        "example": {
          "proofId": "87c16563-a9d9-4bb8-81c7-62b83fce841b",
          "outcome": "erased",
          "erasedAt": 1783460000
        }
      },
      "EmbeddedAssetResponse": {
        "type": "object",
        "required": [
          "url",
          "expiresAt",
          "bytes",
          "sha256",
          "format",
          "publiclyTrusted"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "expiresAt": {
            "$ref": "#/components/schemas/UnixSeconds"
          },
          "bytes": {
            "type": "integer",
            "format": "int64",
            "minimum": 1
          },
          "sha256": {
            "$ref": "#/components/schemas/Sha256"
          },
          "format": {
            "type": "string",
            "minLength": 1
          },
          "publiclyTrusted": {
            "type": "boolean"
          }
        },
        "description": "A short-lived download for the self-contained C2PA audio artifact."
      },
      "Interpretation": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "$ref": "#/components/schemas/InterpretationStatus"
          },
          "text": {
            "type": "string",
            "maxLength": 1500,
            "description": "the high-level summary — present only when `status` is `ready`."
          },
          "generatedAt": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UnixSeconds"
              }
            ],
            "description": "unix seconds when the summary was generated (present when `ready`)."
          },
          "policyVersion": {
            "type": "string",
            "minLength": 1,
            "description": "renderer policy that produced `text`; clients must fail closed on unknown or absent values."
          },
          "retryAfterSeconds": {
            "type": "integer",
            "format": "int32",
            "minimum": 1,
            "description": "suggested delay before the next poll; present when `status` is `pending`"
          }
        },
        "description": "A high-level account of the editing PoH witnessed — a report-only companion to `grade`, produced\nASYNCHRONOUSLY after issuance from content-free behavioral features. A model selects only\napproved evidence IDs through structured output; fixed server\ntemplates render the public text. The model never writes public prose and receives no raw capture,\nexact percentages, names, identifiers, paths, audio, or musical content.\n`pending` while still generating (poll after `retryAfterSeconds`, for at most two minutes per\nforeground polling session); `ready` carries the summary; `unavailable`\nwhen it permanently failed or the proof predates the feature. NON-GATING — never affects\n`good`/`verdict`/issuance.",
        "example": {
          "status": "ready",
          "text": "The track went through repeated note-placement and dynamics changes across the observed session. Most of the visible process was hands-on, while material already present when observation began remains unattributed. This describes the witnessed session, not the music's origin.",
          "policyVersion": "poh-interpretation-policy-2.0.0",
          "generatedAt": 1700000000
        }
      },
      "UploadPurpose": {
        "type": "string",
        "enum": [
          "source",
          "verify"
        ]
      },
      "UploadResponse": {
        "type": "object",
        "required": [
          "uploadId",
          "url",
          "fields",
          "expiresAt",
          "maxBytes"
        ],
        "properties": {
          "uploadId": {
            "type": "string",
            "minLength": 1,
            "description": "the id you pass back to `POST /verify` once the file is uploaded"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "presigned upload endpoint — send a multipart/form-data POST here"
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "form fields (policy, signature, key, …) to send verbatim, with the file as the last part"
          },
          "expiresAt": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UnixSeconds"
              }
            ],
            "description": "unix seconds when this presigned upload policy expires"
          },
          "maxBytes": {
            "type": "integer",
            "format": "int64",
            "minimum": 1,
            "description": "maximum size accepted by this slot (64 MiB normally; 65 MiB for `purpose=verify` to allow C2PA overhead)"
          }
        },
        "description": "A presigned upload: POST the audio as multipart/form-data to `url`, carrying every entry of\n`fields` plus the file. The presigned policy pins a size limit and a server-owned object key, so\nan oversized or misnamed upload is rejected before it is ever stored.",
        "example": {
          "uploadId": "9f3a2c7e-5b14-4d2a-8e6f-1c2b3a4d5e6f",
          "url": "https://uploads.proofofhuman.fm/",
          "fields": {
            "key": "uploads/9f3a2c7e-5b14-4d2a-8e6f-1c2b3a4d5e6f.audio",
            "policy": "eyJleHBpcmF0aW9uIjoiMjAyNi0wNy0wN1QyMzo1OTo1OVoiLCJjb25kaXRpb25zIjpbXX0=",
            "signature": "a1b2c3d4e5f60718293a4b5c6d7e8f90"
          },
          "expiresAt": 1783460900,
          "maxBytes": 67108864
        }
      },
      "VerifyResponse": {
        "type": "object",
        "required": [
          "good",
          "verdict"
        ],
        "properties": {
          "good": {
            "type": "boolean"
          },
          "verdict": {
            "$ref": "#/components/schemas/Verdict"
          },
          "detail": {
            "type": "string"
          },
          "proofId": {
            "type": "string"
          },
          "song": {
            "type": "string"
          },
          "document": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ProofDocument"
              }
            ],
            "description": "The signed public proof document. Present for a valid embedded credential so a one-file\nverifier can render the same receipt as the legacy audio + `.poh` flow without parsing C2PA\nin an untrusted client."
          },
          "classification": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Classification"
              }
            ],
            "description": "The labor score recorded at issuance — what a distributor calls /verify to read (req: \"labor\nscore + cert status\"). NON-GATING: `good`/`verdict` are the cert check and never depend on this.\nReturned to anyone presenting the matching credentialed audio or legacy audio + `.poh` pair —\npossession is the access token. Absent on proofs issued before grading shipped."
          },
          "grade": {
            "$ref": "#/components/schemas/GradeBreakdown"
          },
          "interpretation": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Interpretation"
              }
            ],
            "description": "The async evidence-selected process note recorded for this proof, if any. `pending` while the\nselection job is still running (poll `getInterpretation` by `proofId`), `ready` with the summary,\n`unavailable` / absent on proofs that predate the feature. Report-only like `grade`."
          },
          "embeddedCredential": {
            "allOf": [
              {
                "$ref": "#/components/schemas/EmbeddedCredential"
              }
            ],
            "description": "present when verification used a proof embedded in a C2PA audio asset"
          }
        },
        "example": {
          "good": true,
          "verdict": "valid",
          "proofId": "87c16563-a9d9-4bb8-81c7-62b83fce841b",
          "song": "Fixture Song",
          "classification": "human"
        }
      },
      "VerifyRequest": {
        "type": "object",
        "required": [
          "uploadId"
        ],
        "properties": {
          "uploadId": {
            "type": "string",
            "minLength": 1,
            "description": "the `uploadId` returned by `POST /uploads`, after you've posted the audio bytes to it"
          },
          "poh": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Base64"
              }
            ],
            "description": "The `.poh` proof file, base64-encoded. Omit it when the uploaded audio carries PoH's C2PA\nassertion; the verifier extracts the nested proof after validating the C2PA binding and signer."
          }
        },
        "example": {
          "uploadId": "9f3a2c7e-5b14-4d2a-8e6f-1c2b3a4d5e6f"
        }
      },
      "UnixSeconds": {
        "type": "integer",
        "format": "int64",
        "minimum": 0,
        "description": "Whole unix epoch seconds. Kept numeric on the wire for v1 compatibility."
      },
      "FieldViolation": {
        "type": "object",
        "required": [
          "field",
          "rule",
          "message"
        ],
        "properties": {
          "field": {
            "type": "string",
            "description": "Request location plus field path, e.g. `body.consent.acceptedAt` or `query.days`."
          },
          "rule": {
            "type": "string",
            "description": "Stable schema rule that failed, e.g. `required`, `minimum`, or `pattern`."
          },
          "message": {
            "type": "string",
            "description": "Human-readable description of the violated rule."
          }
        },
        "description": "One invalid request field, derived from the operation's generated OpenAPI schema."
      },
      "PartnerKey": {
        "type": "object",
        "required": [
          "keyId",
          "name",
          "createdAt",
          "status"
        ],
        "properties": {
          "keyId": {
            "type": "string",
            "description": "the key's id (safe to log/display — this is NOT the secret)"
          },
          "name": {
            "type": "string",
            "description": "display name, e.g. `acme-2026-07-08`"
          },
          "createdAt": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UnixSeconds"
              }
            ],
            "description": "unix seconds when the key was created"
          },
          "status": {
            "$ref": "#/components/schemas/PartnerKeyStatus"
          },
          "expiresAt": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UnixSeconds"
              }
            ],
            "description": "unix seconds when an `expiring` key stops working (rotation grace deadline)"
          }
        },
        "description": "One API key on the caller's partner account. Metadata only — the secret is returned exactly once, at rotation."
      },
      "PartnerUsagePlan": {
        "type": "object",
        "properties": {
          "rateLimit": {
            "type": "number",
            "format": "double",
            "minimum": 0,
            "description": "steady-state requests/second the plan allows"
          },
          "burstLimit": {
            "type": "integer",
            "format": "int32",
            "minimum": 0,
            "description": "burst ceiling"
          },
          "quotaLimit": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "the quota amount per period (absent if unlimited)"
          },
          "quotaPeriod": {
            "allOf": [
              {
                "$ref": "#/components/schemas/QuotaPeriod"
              }
            ],
            "description": "the quota period: `DAY`, `WEEK`, or `MONTH`"
          }
        }
      },
      "PartnerDayUsage": {
        "type": "object",
        "required": [
          "date",
          "used"
        ],
        "properties": {
          "date": {
            "type": "string",
            "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
            "description": "the UTC day, `yyyy-mm-dd`"
          },
          "used": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "requests counted against the quota that day"
          },
          "remaining": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "requests left in the quota that day (absent if the plan has no quota)"
          }
        },
        "description": "One UTC day of request usage against the partner's quota."
      },
      "ProofListItem": {
        "type": "object",
        "required": [
          "proofId",
          "song",
          "issuedAt",
          "hasFeedback"
        ],
        "properties": {
          "proofId": {
            "type": "string",
            "minLength": 1
          },
          "song": {
            "type": "string",
            "minLength": 1
          },
          "issuedAt": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UnixSeconds"
              }
            ],
            "description": "unix seconds at issuance — the same value as this proof's canonical `ProofDocument.createdAt`"
          },
          "classification": {
            "$ref": "#/components/schemas/Classification"
          },
          "grade": {
            "allOf": [
              {
                "$ref": "#/components/schemas/GradeBreakdown"
              }
            ],
            "description": "absent on proofs issued before grading shipped"
          },
          "hasFeedback": {
            "type": "boolean",
            "description": "whether the post-export survey was completed — a restored library must not re-ask"
          },
          "embeddedCredential": {
            "allOf": [
              {
                "$ref": "#/components/schemas/EmbeddedCredential"
              }
            ],
            "description": "present when a self-contained C2PA audio artifact is available for this proof"
          }
        },
        "description": "One proof in the caller's library."
      },
      "Classification": {
        "type": "string",
        "enum": [
          "human",
          "ai",
          "unknown"
        ],
        "description": "PoH's classification of the captured session, from the behavioral grader: `human` only when the\nwitnessed hands-on evidence dominates; `ai` only on positive high-risk import, generative, or\nbulk-appearance evidence; `unknown` whenever the trace is too thin to judge —\nabsence of evidence is never read as `ai`. Report-only: issuance is not yet gated on it."
      },
      "GradeBreakdown": {
        "type": "object",
        "required": [
          "human",
          "ai",
          "unknown",
          "riskCategory",
          "rubricVersion"
        ],
        "properties": {
          "human": {
            "type": "integer",
            "format": "int32",
            "minimum": 0,
            "maximum": 100
          },
          "ai": {
            "type": "integer",
            "format": "int32",
            "minimum": 0,
            "maximum": 100
          },
          "unknown": {
            "type": "integer",
            "format": "int32",
            "minimum": 0,
            "maximum": 100
          },
          "riskCategory": {
            "$ref": "#/components/schemas/WorkflowRiskCategory"
          },
          "rubricVersion": {
            "type": "string",
            "minLength": 1
          }
        },
        "description": "The grader's evidence split as display percentages summing to exactly 100, plus the rubric\nversion that produced them (the thresholds/weights are versioned data, so a verdict is always\nreproducible against the rubric that was live at issuance)."
      },
      "EmbeddedCredential": {
        "type": "object",
        "required": [
          "container",
          "format",
          "publiclyTrusted"
        ],
        "properties": {
          "container": {
            "type": "string",
            "enum": [
              "c2pa"
            ]
          },
          "format": {
            "type": "string",
            "minLength": 1
          },
          "publiclyTrusted": {
            "type": "boolean",
            "description": "true only after PoH's production signer chains to the public C2PA Trust List"
          }
        },
        "description": "The portable credential carried inside a C2PA-enabled audio file."
      },
      "Base64": {
        "type": "string",
        "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$",
        "description": "Standard base64-encoded bytes with complete 4-character quanta and valid terminal padding."
      },
      "EraseOutcome": {
        "type": "string",
        "enum": [
          "erased",
          "already_erased"
        ],
        "description": "What an erasure call found: `erased` when this call removed the proof, `already_erased` when there was nothing left to remove."
      },
      "Sha256": {
        "type": "string",
        "pattern": "^[0-9a-f]{64}$",
        "description": "A lowercase hex SHA-256 digest."
      },
      "InterpretationStatus": {
        "type": "string",
        "enum": [
          "pending",
          "ready",
          "unavailable"
        ],
        "description": "Whether the async evidence-selected process note is still generating, ready, or won't be produced."
      },
      "Verdict": {
        "type": "string",
        "enum": [
          "valid",
          "tampered",
          "audioChanged",
          "unregistered",
          "unreadable",
          "local_seal"
        ],
        "description": "The outcome of a verification — `valid` only when the proof is PoH's and matches the audio.\nTreat any verdict you don't recognize as a failed verification: new failure verdicts may be\nadded over time (`local_seal` was added after v1 shipped)."
      },
      "ProofDocument": {
        "type": "object",
        "required": [
          "version",
          "id",
          "createdAt",
          "song",
          "audio",
          "session",
          "statement"
        ],
        "properties": {
          "version": {
            "type": "integer",
            "format": "int32",
            "minimum": 1
          },
          "id": {
            "type": "string",
            "minLength": 1
          },
          "createdAt": {
            "$ref": "#/components/schemas/UnixSeconds"
          },
          "song": {
            "type": "string",
            "minLength": 1
          },
          "audio": {
            "$ref": "#/components/schemas/ProofAudio"
          },
          "session": {
            "$ref": "#/components/schemas/ProofSession"
          },
          "statement": {
            "type": "string",
            "minLength": 1
          }
        },
        "description": "The proof document — the canonical bytes that get signed.",
        "example": {
          "version": 1,
          "id": "87c16563-a9d9-4bb8-81c7-62b83fce841b",
          "createdAt": 1700000000,
          "song": "Fixture Song",
          "audio": {
            "name": "song.wav",
            "format": "WAV",
            "bytes": 44144,
            "sha256": "17718abea891e9406634e02d7a53c8be00bdeb16b41ccece7a1923c4860107a9"
          },
          "session": {
            "sessionId": "fixture1",
            "durationSeconds": 300,
            "noteCount": 48,
            "editCount": 1,
            "commandCount": 0,
            "saveCount": 1
          },
          "statement": "PoH observed this song being edited in the artist's DAW and bound that captured session to this exact audio file — attesting the recorded editing process and the audio's integrity. The record is witnessed, not declared — it informs whoever reads it, and does not by itself certify the music is AI-free."
        }
      },
      "PartnerKeyStatus": {
        "type": "string",
        "enum": [
          "active",
          "expiring",
          "expired",
          "revoked"
        ],
        "description": "A key's complete lifecycle state, including terminal states retained for credential history."
      },
      "QuotaPeriod": {
        "type": "string",
        "enum": [
          "DAY",
          "WEEK",
          "MONTH"
        ]
      },
      "WorkflowRiskCategory": {
        "type": "string",
        "enum": [
          "strong_human",
          "high_risk_import",
          "mixed_assisted",
          "insufficient_evidence",
          "insufficient_data"
        ],
        "description": "The ordered workflow-risk category from the rules cascade — a REVIEW flag layered over the\npercentages, not a verdict. `high_risk_import` means \"finished content arrived with no witnessed\nhuman process; needs review\"; the `insufficient_*` categories mean \"we did not see enough\" and\nmust never be presented as \"judged AI\"."
      },
      "ProofAudio": {
        "type": "object",
        "required": [
          "name",
          "format",
          "bytes",
          "sha256"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "format": {
            "type": "string",
            "minLength": 1
          },
          "bytes": {
            "type": "integer",
            "format": "int64",
            "minimum": 0
          },
          "duration": {
            "type": "number",
            "format": "double",
            "minimum": 0
          },
          "sha256": {
            "$ref": "#/components/schemas/Sha256"
          }
        },
        "description": "The audio a proof is bound to."
      },
      "ProofSession": {
        "type": "object",
        "required": [
          "sessionId",
          "durationSeconds",
          "noteCount",
          "editCount",
          "commandCount",
          "saveCount"
        ],
        "properties": {
          "sessionId": {
            "type": "string",
            "minLength": 1
          },
          "durationSeconds": {
            "type": "number",
            "format": "double",
            "minimum": 0
          },
          "noteCount": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          },
          "editCount": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          },
          "commandCount": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          },
          "saveCount": {
            "type": "integer",
            "format": "int32",
            "minimum": 0
          }
        },
        "description": "The human work behind a proof, distilled from the capture session."
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key"
      }
    }
  },
  "servers": [
    {
      "url": "https://api.proofofhuman.fm/v1",
      "description": "Production",
      "variables": {}
    }
  ],
  "x-poh-auth-surfaces": {
    "partner": "x-api-key on the production partner API",
    "app": "ECDSA-P256 enrolled-device signature in the x-poh-* request headers on the App API",
    "public": "No credential for health, download, and content-free telemetry",
    "proofIdReads": "Capability-addressed but still gateway-authenticated in v1; never anonymous"
  },
  "x-poh-contract": "partner-api"
}
