{
  "openapi": "3.1.0",
  "info": {
    "title": "Policy Controls API",
    "version": "1.0.0",
    "summary": "The clickwrap API, the public document API and proof of record verification.",
    "description": "The reference at https://policycontrols.app/docs explains each call with examples. Every JSON response is an envelope: `{ \"success\": true, \"data\": … }`, or `{ \"success\": false, \"errorCode\": \"…\", \"errorMessage\": \"…\", \"correlationId\": \"…\" }` on an error (the public document API returns the document itself, not an envelope). Branch on `errorCode`; `errorMessage` is for people and may change. This file is checked against the API's routes and request schemas in the tests (packages/backend/src/openapi.test.ts).",
    "contact": {
      "name": "Policy Controls",
      "email": "hello@policycontrols.app",
      "url": "https://policycontrols.app/contact"
    }
  },
  "servers": [
    {
      "url": "https://portal.policycontrols.app/api/v1"
    }
  ],
  "tags": [
    {
      "name": "Clickwrap",
      "description": "From your server only, with a secret API key (Sharing → Clickwrap → API keys). Rate limits: about 600 requests a minute per IP address and 300 per key."
    },
    {
      "name": "Public documents",
      "description": "Documents classified Public, no key needed. CORS is open. Rate limit: about 120 requests a minute per IP address."
    },
    {
      "name": "Verify",
      "description": "Check a proof of record, no key needed. Never shows who accepted. Rate limit: about 60 requests a minute per IP address."
    }
  ],
  "paths": {
    "/clickwrap/confirm": {
      "post": {
        "tags": [
          "Clickwrap"
        ],
        "operationId": "confirmToken",
        "security": [
          {
            "apiKey": []
          }
        ],
        "summary": "Confirm a widget token",
        "description": "When the account is created: the statement, versions, origin, IP and browser come from the token. Tokens last 30 minutes. Safe to repeat for the same user.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConfirmRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/AcceptancesResult"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/AcceptancesResult"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/clickwrap/acceptances": {
      "post": {
        "tags": [
          "Clickwrap"
        ],
        "operationId": "recordAcceptances",
        "security": [
          {
            "apiKey": []
          }
        ],
        "summary": "Record an acceptance without the widget",
        "description": "The current approved version of each document unless you name one. `acceptedAt` defaults to now and can't be in the future or before the version was approved. The location comes from `ip`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AcceptRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/AcceptancesResult"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/AcceptancesResult"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/clickwrap/declines": {
      "post": {
        "tags": [
          "Clickwrap"
        ],
        "operationId": "recordDeclines",
        "security": [
          {
            "apiKey": []
          }
        ],
        "summary": "Record a decline",
        "description": "Either a decline token from the widget's window mode, or `documents` from your own flow (not both). The status API then answers `declined: true` for that version until they accept it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeclineRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/DeclinesResult"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/DeclinesResult"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/clickwrap/status": {
      "get": {
        "tags": [
          "Clickwrap"
        ],
        "operationId": "acceptanceStatus",
        "security": [
          {
            "apiKey": []
          }
        ],
        "summary": "Has this user accepted the current versions?",
        "description": "By `documents` or `group`. A minor version keeps an acceptance current; a major one doesn't, so ask again when `allAccepted` is false.",
        "parameters": [
          {
            "name": "userId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          },
          {
            "name": "documents",
            "in": "query",
            "description": "Comma-separated document numbers or IDs.",
            "schema": {
              "type": "string",
              "maxLength": 2000
            }
          },
          {
            "name": "group",
            "in": "query",
            "description": "A clickwrap group ID, instead of documents.",
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The user's status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/Status"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/clickwrap/acceptances/{receiptId}": {
      "get": {
        "tags": [
          "Clickwrap"
        ],
        "operationId": "getAcceptance",
        "security": [
          {
            "apiKey": []
          }
        ],
        "summary": "Get one acceptance",
        "parameters": [
          {
            "name": "receiptId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,200}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The acceptance.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/Acceptance"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/clickwrap/acceptances/{receiptId}/receipt": {
      "get": {
        "tags": [
          "Clickwrap"
        ],
        "operationId": "getProofOfRecord",
        "security": [
          {
            "apiKey": []
          }
        ],
        "summary": "Download its proof of record",
        "description": "The signed PDF. Acceptances recorded before proofs of record get a receipt PDF instead.",
        "parameters": [
          {
            "name": "receiptId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,200}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The PDF, as an attachment.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "contentMediaType": "application/pdf"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/clickwrap/users/{userId}": {
      "delete": {
        "tags": [
          "Clickwrap"
        ],
        "operationId": "retireUser",
        "security": [
          {
            "apiKey": []
          }
        ],
        "summary": "Retire a user",
        "description": "You deleted or disabled them: no more update emails, and they don't count as needing to accept again. Their acceptances stay as evidence. Safe to repeat.",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "Your own ID for the user.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RetireRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Retired (or already was).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/Retirement"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/clickwrap/users/{userId}/reinstate": {
      "post": {
        "tags": [
          "Clickwrap"
        ],
        "operationId": "reinstateUser",
        "security": [
          {
            "apiKey": []
          }
        ],
        "summary": "Reinstate a retired user",
        "description": "Recording a new acceptance reinstates them too. Safe to repeat.",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "Your own ID for the user.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Active (or already was).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/Retirement"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/public/companies/{companyId}/documents/{docNumber}": {
      "get": {
        "tags": [
          "Public documents"
        ],
        "operationId": "getPublicDocument",
        "security": [],
        "summary": "Get a public document",
        "description": "The latest approved version, or `v` for an earlier one. Anything not public answers exactly like a missing document. Responses carry `Cache-Control` (5 minutes for the latest, an hour for a named version) and an `ETag` of the content hash.",
        "parameters": [
          {
            "name": "companyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "docNumber",
            "in": "path",
            "required": true,
            "description": "The document number (or ID).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "v",
            "in": "query",
            "description": "A version such as 1.3 or v1.3.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "md",
                "pdf",
                "original",
                "csv"
              ],
              "default": "json"
            },
            "description": "`md`: the Markdown (not for PDF or table documents). `pdf`: the PDF. `original`: an uploaded PDF exactly as uploaded (PDF documents only). `csv`: the rows (table documents only)."
          }
        ],
        "responses": {
          "200": {
            "description": "The document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicDocument"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              },
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "contentMediaType": "application/pdf"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "description": "The account is closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "`format=md` for a PDF or table document (`pdf_document`, `table_document`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/verify/{reference}": {
      "get": {
        "tags": [
          "Verify"
        ],
        "operationId": "verifyByReference",
        "security": [],
        "summary": "Check a proof of record by its reference",
        "parameters": [
          {
            "name": "reference",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "examples": [
                "POR-7K2M-Q9XD-4HFA-R3TB"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/ProofVerification"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/verify": {
      "post": {
        "tags": [
          "Verify"
        ],
        "operationId": "verifyByHash",
        "security": [],
        "summary": "Check a file by its SHA-256",
        "description": "Hash the PDF yourself. `fileMatches` says whether it's exactly the file we issued; with a `reference` too, they must agree.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "data"
                  ],
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/ProofVerification"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "A secret API key (`pck_…`), from your server only. Shown once when created; revoke it in Sharing → Clickwrap."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "A field is missing or malformed (`validation`), or a widget token is invalid or expired (`invalid_token`, `token_expired`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The key is missing, unknown or revoked (`invalid_api_key`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The page's origin isn't in the group's allowed origins (`origin_not_allowed`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Not found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "`token_used`, `token_is_decline`, `token_is_accept` or `group_unavailable`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limited. Wait a minute and try again.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "success",
          "errorMessage"
        ],
        "properties": {
          "success": {
            "const": false
          },
          "errorCode": {
            "type": "string"
          },
          "errorMessage": {
            "type": "string"
          },
          "correlationId": {
            "type": "string",
            "description": "Also in the `x-correlation-id` header; quote it if you ask us about a request."
          }
        }
      },
      "User": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "email"
        ],
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Your own ID for the user."
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254
          },
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "organisation": {
            "type": "string",
            "maxLength": 200,
            "description": "Accepting on behalf of an organisation: the statement says so."
          }
        }
      },
      "DocumentRef": {
        "oneOf": [
          {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "A document number or ID: its current approved version."
          },
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "document"
            ],
            "properties": {
              "document": {
                "type": "string",
                "minLength": 1,
                "maxLength": 128
              },
              "version": {
                "type": "string",
                "maxLength": 20,
                "description": "An earlier approved version."
              }
            }
          }
        ]
      },
      "ConfirmRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "token",
          "user"
        ],
        "properties": {
          "token": {
            "type": "string",
            "maxLength": 16000,
            "description": "The `policyControlsToken` field from your form."
          },
          "user": {
            "$ref": "#/components/schemas/User"
          }
        }
      },
      "AcceptRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "documents",
          "user"
        ],
        "properties": {
          "documents": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/DocumentRef"
            }
          },
          "user": {
            "$ref": "#/components/schemas/User"
          },
          "ip": {
            "type": "string",
            "maxLength": 64,
            "description": "The person's IP address; the location comes from it."
          },
          "userAgent": {
            "type": "string",
            "maxLength": 512
          },
          "acceptedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Default now."
          },
          "statement": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000,
            "description": "The exact words your form showed."
          },
          "client": {
            "type": "object",
            "additionalProperties": false,
            "description": "As the person's browser reported them; malformed values are dropped, never an error.",
            "properties": {
              "timeZone": {
                "type": "string",
                "maxLength": 64
              },
              "utcOffsetMinutes": {
                "type": "integer"
              },
              "language": {
                "type": "string",
                "maxLength": 35
              }
            }
          }
        }
      },
      "DeclineRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "user"
        ],
        "description": "Send either `token` or `documents`.",
        "properties": {
          "token": {
            "type": "string",
            "maxLength": 16000,
            "description": "A decline token from the widget's window mode."
          },
          "documents": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/DocumentRef"
            }
          },
          "user": {
            "$ref": "#/components/schemas/User"
          },
          "ip": {
            "type": "string",
            "maxLength": 64
          },
          "userAgent": {
            "type": "string",
            "maxLength": 512
          },
          "declinedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Default now."
          },
          "client": {
            "type": "object",
            "additionalProperties": false,
            "description": "As the person's browser reported them; malformed values are dropped, never an error.",
            "properties": {
              "timeZone": {
                "type": "string",
                "maxLength": 64
              },
              "utcOffsetMinutes": {
                "type": "integer"
              },
              "language": {
                "type": "string",
                "maxLength": 35
              }
            }
          }
        }
      },
      "RetireRequest": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "maxLength": 300
          }
        }
      },
      "VerifyRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "sha256"
        ],
        "properties": {
          "sha256": {
            "type": "string",
            "pattern": "^[0-9a-fA-F]{64}$"
          },
          "reference": {
            "type": "string",
            "maxLength": 40
          }
        }
      },
      "Acceptance": {
        "type": "object",
        "required": [
          "receiptId",
          "document",
          "version",
          "contentHash",
          "statement",
          "acceptedAt",
          "recordedAt",
          "method",
          "user"
        ],
        "properties": {
          "receiptId": {
            "type": "string"
          },
          "created": {
            "type": "boolean",
            "description": "Only when recording: false if it was already recorded."
          },
          "document": {
            "type": "string",
            "description": "The document number."
          },
          "documentId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "contentHash": {
            "type": "string",
            "description": "SHA-256 of the version's content."
          },
          "statement": {
            "type": "string",
            "description": "The exact words agreed to."
          },
          "acceptedAt": {
            "type": "string",
            "format": "date-time"
          },
          "recordedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When Policy Controls recorded it: confirmed by your server (widget) or received through the API. The same as `verifiedAt`."
          },
          "verifiedAt": {
            "type": "string",
            "format": "date-time",
            "description": "The same as `recordedAt`, named as on the timeline."
          },
          "timeline": {
            "type": "array",
            "description": "The moments in order, with where each time came from.",
            "items": {
              "$ref": "#/components/schemas/TimelineEntry"
            }
          },
          "method": {
            "type": "string",
            "enum": [
              "widget",
              "api"
            ]
          },
          "user": {
            "$ref": "#/components/schemas/User"
          },
          "ipAddress": {
            "type": [
              "string",
              "null"
            ]
          },
          "userAgent": {
            "type": [
              "string",
              "null"
            ]
          },
          "location": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "country": {
                "type": "string"
              },
              "city": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "device": {
            "type": [
              "object",
              "null"
            ],
            "description": "Browser, operating system and device type, from the user agent."
          },
          "client": {
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": false,
                "description": "As the person's browser reported them; malformed values are dropped, never an error.",
                "properties": {
                  "timeZone": {
                    "type": "string",
                    "maxLength": 64
                  },
                  "utcOffsetMinutes": {
                    "type": "integer"
                  },
                  "language": {
                    "type": "string",
                    "maxLength": 35
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "groupId": {
            "type": "string",
            "description": "Widget acceptances only."
          },
          "origin": {
            "type": "string",
            "description": "Widget acceptances only: the page's origin."
          }
        }
      },
      "AcceptancesResult": {
        "type": "object",
        "required": [
          "userId",
          "acceptances"
        ],
        "properties": {
          "userId": {
            "type": "string"
          },
          "acceptances": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Acceptance"
            }
          }
        }
      },
      "Decline": {
        "type": "object",
        "required": [
          "declineId",
          "document",
          "version",
          "declinedAt",
          "recordedAt",
          "method",
          "user"
        ],
        "properties": {
          "declineId": {
            "type": "string"
          },
          "created": {
            "type": "boolean"
          },
          "document": {
            "type": "string"
          },
          "documentId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "contentHash": {
            "type": "string"
          },
          "statement": {
            "type": "string",
            "description": "The statement they were shown."
          },
          "declinedAt": {
            "type": "string",
            "format": "date-time"
          },
          "recordedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When Policy Controls recorded it: confirmed by your server (widget) or received through the API. The same as `verifiedAt`."
          },
          "verifiedAt": {
            "type": "string",
            "format": "date-time",
            "description": "The same as `recordedAt`, named as on the timeline."
          },
          "timeline": {
            "type": "array",
            "description": "The moments in order, with where each time came from.",
            "items": {
              "$ref": "#/components/schemas/TimelineEntry"
            }
          },
          "method": {
            "type": "string",
            "enum": [
              "widget",
              "api"
            ]
          },
          "user": {
            "$ref": "#/components/schemas/User"
          }
        }
      },
      "DeclinesResult": {
        "type": "object",
        "required": [
          "userId",
          "declines"
        ],
        "properties": {
          "userId": {
            "type": "string"
          },
          "declines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Decline"
            }
          }
        }
      },
      "Status": {
        "type": "object",
        "required": [
          "userId",
          "retired",
          "allAccepted",
          "documents"
        ],
        "properties": {
          "userId": {
            "type": "string"
          },
          "retired": {
            "type": "boolean"
          },
          "allAccepted": {
            "type": "boolean",
            "description": "Every active, required document's current major version is accepted."
          },
          "documents": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "document": {
                  "type": "string"
                },
                "documentId": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "active",
                    "retired"
                  ]
                },
                "optional": {
                  "type": "boolean",
                  "description": "Only when true: optional in the group."
                },
                "currentVersion": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "accepted": {
                  "type": "boolean"
                },
                "declined": {
                  "type": "boolean"
                },
                "declinedAt": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "acceptedVersion": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "acceptedAt": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "receiptId": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "Retirement": {
        "type": "object",
        "required": [
          "userId",
          "retired",
          "retiredAt"
        ],
        "properties": {
          "userId": {
            "type": "string"
          },
          "retired": {
            "type": "boolean"
          },
          "retiredAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ProofVerification": {
        "type": "object",
        "required": [
          "reference",
          "company",
          "document",
          "kind",
          "acceptedAt",
          "issuedAt",
          "timeline",
          "signed",
          "sha256",
          "documentUnchanged"
        ],
        "properties": {
          "reference": {
            "type": "string"
          },
          "company": {
            "type": "string",
            "description": "The account's name."
          },
          "document": {
            "type": "object",
            "properties": {
              "number": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "version": {
                "type": "string"
              },
              "contentHash": {
                "type": "string"
              }
            }
          },
          "kind": {
            "type": "string",
            "enum": [
              "acceptance link",
              "clickwrap"
            ]
          },
          "acceptedAt": {
            "type": "string",
            "format": "date-time"
          },
          "issuedAt": {
            "type": "string",
            "format": "date-time"
          },
          "timeline": {
            "type": "array",
            "description": "The moments in order, with where each time came from.",
            "items": {
              "$ref": "#/components/schemas/TimelineEntry"
            }
          },
          "signed": {
            "type": "boolean"
          },
          "sha256": {
            "type": "string",
            "description": "SHA-256 of the proof PDF as issued."
          },
          "documentUnchanged": {
            "type": "boolean",
            "description": "The version's content hash still matches the one accepted."
          },
          "fileMatches": {
            "type": "boolean",
            "description": "Only when a sha256 was sent."
          }
        }
      },
      "PublicDocument": {
        "type": "object",
        "required": [
          "company",
          "docNumber",
          "title",
          "version",
          "isLatest",
          "latestVersion",
          "contentHash",
          "format",
          "versions"
        ],
        "properties": {
          "company": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "documentId": {
            "type": "string"
          },
          "docNumber": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "classification": {
            "const": "Public"
          },
          "documentStatus": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "versionStatus": {
            "type": "string",
            "enum": [
              "approved",
              "superseded"
            ]
          },
          "isLatest": {
            "type": "boolean"
          },
          "latestVersion": {
            "type": "string"
          },
          "approvedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "effectiveDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "approverName": {
            "type": [
              "string",
              "null"
            ]
          },
          "changeSummary": {
            "type": [
              "string",
              "null"
            ]
          },
          "whatChanged": {
            "type": "string"
          },
          "contentHash": {
            "type": "string"
          },
          "contentHashAlgorithm": {
            "type": "string"
          },
          "format": {
            "type": "string",
            "enum": [
              "markdown",
              "pdf",
              "table"
            ]
          },
          "versions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "version": {
                  "type": "string"
                },
                "approvedAt": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "effectiveDate": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "changeSummary": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "markdown": {
            "type": "string",
            "description": "Markdown documents."
          },
          "pdfUrl": {
            "type": "string",
            "format": "uri",
            "description": "Markdown and table documents."
          },
          "pdf": {
            "type": "object",
            "description": "PDF documents.",
            "properties": {
              "url": {
                "type": "string"
              },
              "originalUrl": {
                "type": "string"
              },
              "filename": {
                "type": "string"
              },
              "pages": {
                "type": "integer"
              },
              "size": {
                "type": "integer"
              },
              "sha256": {
                "type": "string"
              },
              "signed": {
                "type": "boolean"
              },
              "coverPage": {
                "type": "boolean"
              }
            }
          },
          "text": {
            "type": [
              "string",
              "null"
            ],
            "description": "PDF documents: the extracted text."
          },
          "table": {
            "type": "object",
            "description": "Table documents.",
            "properties": {
              "columns": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "rowCount": {
                "type": "integer"
              },
              "rows": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "csvUrl": {
                "type": "string"
              },
              "asAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "TimelineEntry": {
        "type": "object",
        "description": "One moment in an acceptance's timeline, in order (Presented, Accepted or Declined, Verified, Proof issued; for acceptance links Sent, First opened, Accepted, Proof issued).",
        "required": [
          "step",
          "label",
          "at",
          "source"
        ],
        "properties": {
          "step": {
            "type": "string",
            "enum": [
              "sent",
              "opened",
              "presented",
              "accepted",
              "declined",
              "verified",
              "issued"
            ]
          },
          "label": {
            "type": "string",
            "description": "As printed on the proof, e.g. \"Verified\"."
          },
          "at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601, UTC."
          },
          "source": {
            "type": "string",
            "enum": [
              "server",
              "widget",
              "customer"
            ],
            "description": "Whose clock gave the time: Policy Controls' server, the widget's tick (timed by our server when the widget asked for its token), or your server (sent through the API)."
          },
          "note": {
            "type": "string",
            "description": "What the moment was, e.g. \"confirmed by the customer's server\"."
          }
        }
      }
    }
  }
}