openapi: 3.1.0
info:
  title: Unforged folio API
  version: 2026-10-14
  summary: "Retention-enforced document storage: filing, records, reasons, holds, exports and the ledger."
  description: |
    folio's routes, under `/folio/`. Every route answers 404 if folio has been switched off for
    your organisation. The platform routes (keys, the audit log, billing) are in the platform
    document and the Nib routes in Nib's.

    **Authentication.** Send `Authorization: Bearer <API key>`, and treat the whole key as a secret.
    The one exception is a download link (`GET /folio/links/{token}`): the link is the credential.

    **Regions.** Your organisation's data lives in its home region, fixed at creation. Call
    `https://api.unforged.sh` or your region's host, `https://<region>.api.unforged.sh`; another
    region's host answers 421 `wrong_region`.

    **Access.** Every route needs one permission, shown as `x-permission`; without it the answer is
    403 `permission_denied`. Ids are scoped to your organisation. Permissions and default roles:
    https://unforged.ai/security#access.

    **Reasons.** Filing needs no reason. Opening, downloading or exporting a record needs one where
    your organisation's settings or a protected folder say so: name an open reason
    (`POST /folio/reasons`) in `Unforged-Reason-Id`, or give one for the call with
    `Unforged-Reason-Code` and `Unforged-Reason`. Without it the answer is 428 `reason_required`.
    Changes and deletions carry their reason in the body. Every read, change and refusal is in
    the ledger.

    **Errors** are RFC 9457 `application/problem+json` documents with a stable `code` (see
    `Problem`). Every response carries `X-Request-Id`.

    **Idempotency.** Most POSTs need an `Idempotency-Key`, kept for 24 hours. The same key and body
    replays the response; a different body is 409 `idempotency_conflict`. Filing is idempotent on
    its own as well: the same folder, name and bytes return the same receipt.

    **Billing.** While an account is overdue, filing and removal answer 402 `billing_required`;
    after the notice period everything but usage answers 402 `access_suspended`. Nothing is
    deleted for non-payment.

    **Versioning.** The API version is a date, sent as `Unforged-Version: YYYY-MM-DD`. Your
    organisation is pinned to the version current when it was created; the header overrides the pin
    for one request. An unknown version is 400 `invalid_version`.

    Additive changes never create a version: new endpoints, optional parameters, response fields
    and open enum values reach every version. **Clients must ignore unknown fields.** Breaking
    changes ship only in a new dated version.

    **Deprecation.** A version is supported for 24 months after it is replaced. Responses on a
    deprecated version carry `Deprecation`, `Sunset` and a `Link` to the upgrade guide.

    **Webhooks.** `folio.record.filed`, `folio.hold.applied`, `folio.hold.released`,
    `folio.record.disposed`, `folio.integrity.alert`, `folio.export.ready` and
    `folio.restore.ready`, signed with `Unforged-Signature` exactly as Nib's (see the webhooks
    guide).
  contact:
    name: Unforged
    url: https://unforged.sh
servers:
  - url: https://api.unforged.sh
security:
  - apiKey: []
  - userToken: []
tags:
  - name: folio
    description: Retention-enforced document storage.
paths:
  /folio/records:
    post:
      tags:
        - folio
      operationId: fileRecord
      x-permission: folio:records:write
      summary: File a document
      description: "The request body is the file (at most 50 MB here; for a larger file, up to 2 GiB, use `POST /folio/uploads`, or the S3 gateway). Filing needs no reason. The file is scanned for malware first: an infected file is refused (`400`, ledgered, nothing stored) and while no scanner can answer filing is refused (`503 not_ready`). The response is the receipt: the document's SHA-256, its retention class and lock, and the ledger position, returned once the object is locked. Folder and ancestors are created; the folder's matter and subject are inherited. A mistaken filing cannot be edited or replaced: file the correct document separately."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: folder
          in: query
          schema:
            type: string
            default: ""
        - name: name
          in: query
          required: true
          schema:
            type: string
        - name: class
          in: query
          schema:
            type: string
          description: The retention class; the organisation's default when omitted.
        - name: matter
          in: query
          schema:
            type: string
        - name: subject
          in: query
          schema:
            type: string
        - name: source_system
          in: query
          schema:
            type: string
          description: An import's origin (kept as provenance and ledgered as `record.imported`).
        - name: source_ref
          in: query
          schema:
            type: string
        - name: original_date
          in: query
          schema:
            type: string
            format: date
          description: The original created or event date; starts retention for classes that count from an event.
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "201":
          description: Filed.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioReceipt"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/Unavailable"
        default:
          $ref: "#/components/responses/Internal"
    get:
      tags:
        - folio
      operationId: listRecords
      x-permission: folio:records:read
      summary: List records
      description: Listing needs no reason but is audited; the ledger records what was listed and which records were shown.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: folder
          in: query
          schema:
            type: string
            default: ""
        - name: recursive
          in: query
          schema:
            type: boolean
            default: false
        - name: after
          in: query
          schema:
            type: string
          description: The previous page's `next_after`.
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        "200":
          description: A page of records.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioRecordPage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/records/{id}:
    get:
      tags:
        - folio
      operationId: getRecord
      x-permission: folio:records:read
      summary: Open a record
      description: The record's details and receipt. A view, so a reason is required where the organisation setting or a protected folder says so.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: id
          in: path
          required: true
          schema:
            type: string
        - $ref: "#/components/parameters/FolioReasonId"
        - $ref: "#/components/parameters/FolioReasonCode"
        - $ref: "#/components/parameters/FolioReasonText"
      responses:
        "200":
          description: The record.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioRecord"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "428":
          $ref: "#/components/responses/FolioReasonRequired"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/records/{id}/content:
    get:
      tags:
        - folio
      operationId: getRecordContent
      x-permission: folio:documents:read
      summary: Download a document
      description: "The ledger entry is committed before any byte is returned. The stored bytes are checked against the receipt's hash on every whole read; a mismatch is an integrity alert and the transfer is cut short. Send `Range` to read part of a document (one range, at most 64 MiB a time): the answer is `206` with `Content-Range`, and each such request is a view like any other. A range that starts past the end is `416`, and nothing is ledgered or counted. Every read counts the bytes it sends toward the download allowance."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: id
          in: path
          required: true
          schema:
            type: string
        - $ref: "#/components/parameters/FolioReasonId"
        - $ref: "#/components/parameters/FolioReasonCode"
        - $ref: "#/components/parameters/FolioReasonText"
        - $ref: "#/components/parameters/FolioRange"
        - $ref: "#/components/parameters/FolioIfRange"
      responses:
        "200":
          description: The document.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
            Accept-Ranges:
              $ref: "#/components/headers/FolioAcceptRanges"
            ETag:
              $ref: "#/components/headers/FolioETag"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "206":
          $ref: "#/components/responses/FolioPartialContent"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "416":
          $ref: "#/components/responses/FolioRangeNotSatisfiable"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "428":
          $ref: "#/components/responses/FolioReasonRequired"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/reasons:
    post:
      tags:
        - folio
      operationId: openReason
      x-permission: folio:records:read
      summary: Open a reason
      description: A reason is given once for a context (a folder and its sub-folders, a matter, a subject or a list of records) and covers every associated record until it expires, at most 20 minutes, or is ended. Pass its id as `Unforged-Reason-Id`.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioReasonRequest"
      responses:
        "201":
          description: Opened.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioReason"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/reasons/{id}:
    delete:
      tags:
        - folio
      operationId: endReason
      x-permission: folio:records:read
      summary: End a reason
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Ended (ending an unknown or ended reason is not an error).
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/holds:
    post:
      tags:
        - folio
      operationId: placeHold
      x-permission: folio:holds:manage
      summary: Place a legal hold
      description: Suspends disposal of every record in the scope (a record, a folder and everything under it, a matter or a subject) until the hold is released. A change, so a reason is required; every covered record is ledgered. Records filed into the scope later are covered too.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioHoldRequest"
      responses:
        "201":
          description: Placed.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioHold"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
    get:
      tags:
        - folio
      operationId: listHolds
      x-permission: folio:holds:manage
      summary: List holds
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: include_released
          in: query
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: The holds, newest first.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                type: object
                required:
                  - holds
                properties:
                  holds:
                    type: array
                    items:
                      $ref: "#/components/schemas/FolioHold"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/holds/{id}/release:
    post:
      tags:
        - folio
      operationId: requestHoldRelease
      x-permission: folio:holds:manage
      summary: Ask to release a hold
      description: Nothing is released until a different person approves (`POST /folio/approvals/{id}/approve`).
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioReasonOnly"
      responses:
        "201":
          description: The approval request.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioApproval"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/approvals/{id}/approve:
    post:
      tags:
        - folio
      operationId: approveRequest
      x-permission: folio:holds:approve
      summary: Approve a request
      description: The approver must be a different person from the requester (409 otherwise), with a reason of their own. The request expires after seven days and is used once.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioReasonOnly"
      responses:
        "200":
          description: Approved and carried out.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioApproval"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/approvals/{id}/refuse:
    post:
      tags:
        - folio
      operationId: refuseRequest
      x-permission: folio:holds:approve
      summary: Refuse a request
      description: The refuser must be a different person from the requester (409 otherwise), with a reason of their own, and needs what approving it would need. Nothing the request asked for happens; the request is then `refused` and can never be approved. A requester who changes their mind leaves it to expire.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioReasonOnly"
      responses:
        "200":
          description: Refused.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioApproval"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/records/{id}/retention:
    post:
      tags:
        - folio
      operationId: extendRetention
      x-permission: folio:records:write
      summary: Extend a record's retention
      description: Retention only moves later. The object store's lock is extended first, then the record and the ledger.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioRetentionRequest"
      responses:
        "200":
          description: Extended.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                type: object
                required:
                  - record_id
                  - earliest_delete
                properties:
                  record_id:
                    type: string
                  earliest_delete:
                    type: string
                    format: date
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/records/{id}/early-delete:
    post:
      tags:
        - folio
      operationId: requestEarlyDelete
      x-permission: folio:records:delete
      summary: Ask to delete a record before its retention ends
      description: Only Retained records; a Locked record has no such path (409). Nothing is deleted until a different person approves with `POST /folio/approvals/{id}/approve`; a record under a legal hold cannot be deleted until the hold is released. The disposition certificate says the deletion was early and names both people.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioReasonOnly"
      responses:
        "201":
          description: The approval request.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioApproval"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/classes:
    get:
      tags:
        - folio
      operationId: listClasses
      x-permission: folio:records:read
      summary: List retention classes
      description: Every class at its current version. A new organisation has one, Standard (7 years, Retained).
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
      responses:
        "200":
          description: The classes.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                type: object
                required:
                  - classes
                properties:
                  classes:
                    type: array
                    items:
                      $ref: "#/components/schemas/FolioClass"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
    post:
      tags:
        - folio
      operationId: saveClass
      x-permission: folio:settings:manage
      summary: Create a class or a new version of one
      description: 'Classes are versioned; every record keeps the version it was filed under, so a shorter period applies to new filings only. A change, so a reason is required. Locked classes need the Locked add-on. A version that protects less needs a second person: Locked to Retained, "ask first" to automatic deletion, a shorter notice, a colder storage profile, or (for the default class) a shorter period. The answer is then `202` with the approval, and the version is written when a different person approves (`POST /folio/approvals/{id}/approve`).'
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioClassRequest"
      responses:
        "201":
          description: Saved.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioClass"
        "202":
          description: Waiting for a second person.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioApproval"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/classes/{id}/extension:
    get:
      tags:
        - folio
      operationId: previewClassExtension
      x-permission: folio:settings:manage
      summary: Preview applying a class to existing records
      description: What applying the class's latest version to its older records would change.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The preview.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioExtensionPreview"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/classes/{id}/extend:
    post:
      tags:
        - folio
      operationId: requestClassExtension
      x-permission: folio:settings:manage
      summary: Ask to apply a class to existing records
      description: Records only ever move later. Nothing changes until a different person approves with `POST /folio/approvals/{id}/approve`; the request records the preview.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioReasonOnly"
      responses:
        "201":
          description: The approval request.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioApproval"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/ledger:
    get:
      tags:
        - folio
      operationId: listLedger
      x-permission: folio:audit:read
      summary: Read the ledger
      description: "The hash-chained event ledger, newest first. It holds who, what, where and why, never document content. Reading it is itself ledgered (`ledger.read`). `head_keys` lists the public key of every stamp that signed one of the organisation's ledger heads (more than one after a move to another region). They come from the same database as the heads, so do not trust them as served: check each against the published key of the stamp it names (`GET /folio/head-key` on that region's host) before giving it to the offline verifier."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: actor
          in: query
          schema:
            type: string
        - name: action
          in: query
          schema:
            type: string
          description: An action prefix, such as `record.` or `search.run`.
        - name: target
          in: query
          schema:
            type: string
          description: A record id, or a folder and everything under it.
        - name: from
          in: query
          schema:
            type: integer
          description: Unix milliseconds, inclusive.
        - name: to
          in: query
          schema:
            type: integer
          description: Unix milliseconds, exclusive.
        - name: before
          in: query
          schema:
            type: integer
          description: The previous page's `next_before`.
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        "200":
          description: A page of ledger entries.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioLedgerPage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/usage:
    get:
      tags:
        - folio
      operationId: getFolioUsage
      x-permission: org:read
      summary: This month's usage
      description: This month's usage by item, the storage held per profile, the download allowance and a forecast. Amounts are null (a placeholder) until prices are set.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
      responses:
        "200":
          description: This month's usage.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                type: object
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/usage.csv:
    get:
      tags:
        - folio
      operationId: getFolioUsageCsv
      x-permission: org:read
      summary: This month's usage as CSV
      description: The same month as CSV; it reconciles with the usage lines.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
      responses:
        "200":
          description: This month's usage as CSV.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            text/csv:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/grants:
    post:
      tags:
        - folio
      operationId: requestFolioGrant
      x-permission: folio:settings:manage
      summary: Ask for short-lived regulator access
      description: "While access is suspended for non-payment, ask for short-lived access to respond to a regulator: the regulator and its reference, the folders or matters needed, the people who need access, 24, 48 or 72 hours (48 by default) and a declaration. Two members of Unforged staff approve it; they never see a document. Once approved, the named people can read, search and export the records in the named folders, matters or subjects for that time only (listing and search show only those), and every access is recorded with the reason regulatory_request and the reference. Five grants in any rolling 12 months; later ones carry a one-off fee. Allowed while suspended."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - regulator
                - reference
                - people
                - declaration
              properties:
                regulator:
                  type: string
                  maxLength: 200
                reference:
                  type: string
                  maxLength: 200
                folders:
                  type: array
                  items:
                    type: string
                matters:
                  type: array
                  items:
                    type: string
                subjects:
                  type: array
                  items:
                    type: string
                people:
                  type: array
                  items:
                    type: string
                  minItems: 1
                  maxItems: 20
                hours:
                  type: integer
                  enum:
                    - 24
                    - 48
                    - 72
                  default: 48
                declaration:
                  type: string
                  maxLength: 2000
      responses:
        "201":
          description: Requested; it waits for two approvals.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                type: object
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
    get:
      tags:
        - folio
      operationId: listFolioGrants
      x-permission: folio:settings:manage
      summary: List regulator grants
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
      responses:
        "200":
          description: The grants, newest first.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                type: object
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/search:
    get:
      tags:
        - folio
      operationId: searchRecords
      x-permission: folio:documents:search
      summary: Search records
      description: '`scope=names` (the default) searches names, matters and subjects. `scope=content` searches inside the documents: words (all required), "exact phrases", `"two words"~5` for proximity, `OR`, `AND`, `NOT` or a leading `-`, parentheses, wildcards (`regist*`, `wo?en`) and fuzzy words for OCR errors (`registry~`, `registry~2`), with metadata filters. Content hits list their matching pages and, for callers who may view documents, snippets cut from the extracted text. The result counts the records in scope that are not yet searchable or were read with low confidence; finding nothing is not proof that a word is absent. The words inside are read from PDFs, scans and photos, plain text, Word, Excel and PowerPoint files, OpenDocument and RTF files, and emails (.eml, with the names of their attachments). Older Office files (.doc, .xls, .ppt), Outlook .msg files, zip archives and password-protected documents are found by name, matter and subject only. No reason is needed; the search, its filters and everything shown are ledgered.'
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: q
          in: query
          required: true
          schema:
            type: string
            maxLength: 500
        - name: scope
          in: query
          schema:
            type: string
            enum:
              - names
              - content
            default: names
        - name: folder
          in: query
          schema:
            type: string
            default: ""
        - name: matter
          in: query
          description: Content scope only.
          schema:
            type: string
        - name: subject
          in: query
          description: Content scope only.
          schema:
            type: string
        - name: class
          in: query
          description: A retention class id (content scope only).
          schema:
            type: string
        - name: lock
          in: query
          description: Content scope only.
          schema:
            type: string
            enum:
              - retained
              - locked
        - name: from
          in: query
          description: Filed at or after, Unix milliseconds (content scope only).
          schema:
            type: integer
            format: int64
        - name: to
          in: query
          description: Filed before, Unix milliseconds (content scope only).
          schema:
            type: integer
            format: int64
        - name: held
          in: query
          description: Only records under a legal hold, or only records not under one (content scope only).
          schema:
            type: boolean
        - name: limit
          in: query
          description: At most 200 for names, 50 for content.
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        "200":
          description: Matching records.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioSearchResult"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/erasure:
    get:
      tags:
        - folio
      operationId: assessErasure
      x-permission: folio:records:read
      summary: Assess an erasure request
      description: "Lists the subject's records and, for each, whether it could be erased and why not: `due` (retention has ended), `retained` (inside a Retained class), `locked` (inside a Locked class: never erasable early) or `held` (under a legal hold, with the hold's reason). Ledgered as an audited read."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: subject
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The assessment.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioErasureAssessment"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
    post:
      tags:
        - folio
      operationId: requestErasure
      x-permission: folio:records:delete
      summary: Ask to erase a subject's records
      description: "Erasure is not absolute: retention required by law takes precedence. A record under a legal hold or in a Locked class inside its retention is never erased. Records past their retention are erased; records inside a Retained class's retention only when `include_retained` is set, which makes the two approvers' decision explicit. Nothing is erased until a different person approves with `POST /folio/approvals/{id}/approve`; the completed approval carries a `result` report naming what was erased and what was kept and why. 409 when nothing can be erased now."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioErasureRequest"
      responses:
        "201":
          description: The approval request.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioApproval"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/folders/protect:
    post:
      tags:
        - folio
      operationId: protectFolder
      x-permission: folio:settings:manage
      summary: Protect a folder
      description: Views of records in the folder and every folder below it need a reason even when the organisation's view-reason setting is off. A change, so a reason is required; ledgered.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioFolderRequest"
      responses:
        "200":
          description: Protected.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioFolderProtected"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/folders/unprotect:
    post:
      tags:
        - folio
      operationId: requestFolderUnprotect
      x-permission: folio:settings:manage
      summary: Ask to remove a folder's protection
      description: Nothing changes until a different person approves (`POST /folio/approvals/{id}/approve`, which then also needs `folio:settings:manage`). `409` when the folder is not protected, or a folder above it is (a sub-folder cannot opt out of its parent's protection).
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioFolderRequest"
      responses:
        "201":
          description: The approval request.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioApproval"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/folders/rename:
    post:
      tags:
        - folio
      operationId: renameFolder
      x-permission: folio:records:write
      summary: Rename a folder
      description: Renames the last segment of `path`. Everything in the folder and below it keeps its place, and its path changes with it. A change, so a reason with details is required; every document gets its own ledger entry. `409` with a sentence when the folder holds a hold, an open reason, a regulator grant, a waiting approval, queued or open work, a document still being filed, more than 2,000 documents, or when the new name already exists. A protected folder returns `202` with an approval (`rename_folder`) that a different person decides, which then also needs `folio:settings:manage`.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioFolderRename"
      responses:
        "200":
          description: The rename.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioFolderRenamed"
        "202":
          description: The approval request; nothing has changed yet.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioApproval"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/records/{id}/move:
    post:
      tags:
        - folio
      operationId: moveRecord
      x-permission: folio:records:write
      summary: Move a record to another folder
      description: A change, so a reason is required; ledgered with the folder before and after. The stored file, its hashes, class and retention never change. Moving a record out of a protected folder answers `202` with the approval a different person must give (`POST /folio/approvals/{id}/approve`, which then also needs `folio:records:write`). `409` when the record is under a hold or the folder already has a file of that name.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioFolderRequest"
      responses:
        "200":
          description: Moved.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioMove"
        "202":
          description: Waiting for a second person.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioApproval"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/approvals:
    get:
      tags:
        - folio
      operationId: listApprovals
      x-permission: folio:records:read
      summary: List two-person requests, pending or by how they ended
      description: "Requests newest first: by default those not yet decided and not expired. `state` picks `pending` (the default), `approved`, `refused` or `expired` (never decided in time), so a requester can see how their requests ended. `whose`: `mine` (requests the caller made), `others` (requests the caller could approve; listed only for a caller with `folio:holds:approve`) or `all` (the default); `mine=true` is the same as `whose=mine`. Page with `before` (the `next_before` of the previous page)."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: whose
          in: query
          schema:
            type: string
            enum:
              - mine
              - others
              - all
            default: all
        - name: state
          in: query
          schema:
            type: string
            enum:
              - pending
              - approved
              - refused
              - expired
            default: pending
        - name: mine
          in: query
          schema:
            type: boolean
            default: false
        - name: before
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        "200":
          description: The requests in the chosen state (pending unless `state` says otherwise), filtered by `whose` or `mine`.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioApprovalList"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/filings:
    post:
      tags:
        - folio
      operationId: queueFiling
      x-permission: folio:records:write
      summary: Queue a document for filing
      description: "The request body is the file. For bulk and import callers: the file is checked, held in folio's staging area and filed by a background job through the same pipeline as `POST /folio/records` (the same scan, rules, ledger and billing). Follow it with `GET /folio/filings/{id}`. No reason is needed. A file over 50 MB goes through `POST /folio/uploads` instead."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: folder
          in: query
          schema:
            type: string
            default: ""
        - name: name
          in: query
          required: true
          schema:
            type: string
        - name: class
          in: query
          schema:
            type: string
          description: The retention class; the organisation's default when omitted.
        - name: matter
          in: query
          schema:
            type: string
        - name: subject
          in: query
          schema:
            type: string
        - name: source_system
          in: query
          schema:
            type: string
          description: An import's origin (kept as provenance and ledgered as `record.imported`).
        - name: source_ref
          in: query
          schema:
            type: string
        - name: original_date
          in: query
          schema:
            type: string
            format: date
          description: The original created or event date; starts retention for classes that count from an event.
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "202":
          description: Queued.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioQueuedFiling"
              example:
                id: fil_01J9X3M8V7K2Q4R5S6T7U8V9W0
                state: queued
                folder: import
                name: letter.pdf
                size: 18234
                sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
                record_id: null
                error: null
                created_at_ms: 1790000000000
                finished_at_ms: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/filings/{id}:
    get:
      tags:
        - folio
      operationId: getQueuedFiling
      x-permission: folio:records:read
      summary: A queued filing
      description: "`queued`, then `filed` with its `record_id`, `refused` with why (a bad name, a clash, a file the malware scan refused), or `failed` after the last retry."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/FolioId"
      responses:
        "200":
          description: The queued filing.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioQueuedFiling"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/uploads:
    post:
      tags:
        - folio
      operationId: startFolioUpload
      x-permission: folio:records:write
      summary: Start a large upload
      description: For a file over 50 MB, up to 2 GiB (2147483648 bytes). The file goes in parts straight to your organisation's own intake storage, then it is scanned whole and filed through the same pipeline as `POST /folio/records`. Give the filing's options and the file's `size`; the answer says how big each part is and how many there are. Then sign parts with `POST /folio/uploads/{id}/parts`, `PUT` each one to its URL, finish with `POST /folio/uploads/{id}/complete` and follow `GET /folio/uploads/{id}` to the record. `413 too_large` over 2 GiB; `409` when the name is taken in that folder, the same file is already on its way, or your organisation has too many large uploads open; `503 org_moving` while your organisation is moving region. Open to API keys with `folio:records:write`.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioUploadStart"
            example:
              folder: scans/2026
              name: deeds-bundle.pdf
              size: 734003200
      responses:
        "201":
          description: Started.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioUploadStarted"
              example:
                id: lup_01J9X3M8V7K2Q4R5S6T7U8V9W0
                state: uploading
                size: 734003200
                part_size: 33554432
                part_count: 22
                max_parts_in_flight: 4
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/Unavailable"
        default:
          $ref: "#/components/responses/Internal"
    get:
      tags:
        - folio
      operationId: listFolioUploads
      x-permission: folio:records:read
      summary: Your open large uploads
      description: The caller's own large uploads that are still `uploading` or `checking`, newest first (at most 100), and `intake_origin`, the origin every part's URL names. Choosing the same file again carries on where an upload stopped.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
      responses:
        "200":
          description: The open uploads.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioUploadList"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/uploads/{id}:
    get:
      tags:
        - folio
      operationId: getFolioUpload
      x-permission: folio:records:read
      summary: A large upload
      description: "`uploading` (with the parts that have arrived, each with its SHA-256, so carrying on sends only what is missing or different), then `checking` once it is complete, then `filed` with its `record_id`, `refused` with why (the malware scan, a file the scanner could not check whole, a clash), `failed` after the last retry, or `aborted`. Only the person or key that started it sees it; anyone else gets `404`."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The upload.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioUpload"
              example:
                id: lup_01J9X3M8V7K2Q4R5S6T7U8V9W0
                state: checking
                folder: scans/2026
                name: deeds-bundle.pdf
                size: 734003200
                part_size: 33554432
                part_count: 22
                parts: []
                bytes_received: 734003200
                record_id: null
                error: null
                created_at_ms: 1790000000000
                updated_at_ms: 1790000420000
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
    delete:
      tags:
        - folio
      operationId: abortFolioUpload
      x-permission: folio:records:write
      summary: Stop a large upload
      description: Stops an upload that is still `uploading`; whatever was sent is thrown away. One that has already finished answers as it stands; one that is `checking` can't be stopped (`409`).
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The upload, `aborted`.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioUpload"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/uploads/{id}/parts:
    post:
      tags:
        - folio
      operationId: signFolioUploadParts
      x-permission: folio:records:write
      summary: Sign parts of a large upload
      description: Up to 8 parts at a time. Each part is named by its `number` (from 1), its `size` (every part but the last is exactly `part_size` bytes) and the base64 SHA-256 of its bytes. Send each part with the `method`, to the `url`, with exactly the `headers` given, before `expires_at_ms` (15 minutes); a part whose bytes don't match its SHA-256 is refused by storage and can be signed and sent again.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioUploadSignRequest"
            example:
              parts:
                - number: 1
                  size: 33554432
                  sha256: n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
      responses:
        "200":
          description: The signed parts.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioUploadSigned"
              example:
                parts:
                  - number: 1
                    method: PUT
                    url: https://unf-prod-org-01j9x3m8v7-folio-intake.s3.eu-west-2.amazonaws.com/u/lup_01J9X3M8V7K2Q4R5S6T7U8V9W0?partNumber=1&uploadId=example&X-Amz-Algorithm=AWS4-HMAC-SHA256
                    headers:
                      x-amz-checksum-sha256: n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
                    expires_at_ms: 1790000900000
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/uploads/{id}/complete:
    post:
      tags:
        - folio
      operationId: completeFolioUpload
      x-permission: folio:records:write
      summary: Finish a large upload
      description: "List every part once, with the SHA-256 you signed it for. The parts storage holds are checked against that list and the file's size, then the file is put together and queued to be scanned and filed: `202` with the upload `checking`. Calling it again answers the upload as it stands. `400` when a part is missing or isn't the one that was signed: send that part again and finish again."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioUploadCompleteRequest"
      responses:
        "202":
          description: Complete, and queued for filing.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioUpload"
              example:
                id: lup_01J9X3M8V7K2Q4R5S6T7U8V9W0
                state: checking
                folder: scans/2026
                name: deeds-bundle.pdf
                size: 734003200
                part_size: 33554432
                part_count: 22
                parts: []
                bytes_received: 734003200
                record_id: null
                error: null
                created_at_ms: 1790000000000
                updated_at_ms: 1790000420000
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/records/from-check:
    post:
      tags:
        - folio
      operationId: fileRecordFromCheck
      x-permission: folio:records:write
      summary: File a Nib check's document with its result
      description: "Copies the checked document and its result into folio: the result is attached to the record as hashed evidence and the record's provenance names the check. The check and its result are unchanged. Also needs `nib:checks:read`. `409` when Nib no longer keeps the document."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioFromCheck"
      responses:
        "201":
          description: Filed.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioReceipt"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/folders:
    get:
      tags:
        - folio
      operationId: listFolioFolders
      x-permission: folio:records:read
      summary: List folders
      description: The folders directly below `parent` (the top level when omitted), by path.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: parent
          in: query
          schema:
            type: string
            default: ""
        - name: after
          in: query
          schema:
            type: string
          description: The previous page's `next_after`.
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
      responses:
        "200":
          description: A page of folders.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioFolderPage"
              example:
                folders:
                  - path: clients
                    matter: null
                    subject: null
                    protected: false
                    created_at_ms: 1790000000000
                next_after: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
    post:
      tags:
        - folio
      operationId: createFolioFolder
      x-permission: folio:records:write
      summary: Create a folder
      description: Creates the folder and any missing ancestors. Its matter and subject are inherited by what is filed into it. No reason is needed; ledgered as `folder.created`. `409` if it exists.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioFolderCreate"
      responses:
        "201":
          description: Created.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioFolder"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/folders/detail:
    get:
      tags:
        - folio
      operationId: getFolioFolder
      x-permission: folio:records:read
      summary: A folder
      description: One folder, with the number of filed records and folders directly in it.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: path
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The folder.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioFolderDetail"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/records/{id}/custody:
    get:
      tags:
        - folio
      operationId: getFolioCustody
      x-permission: folio:exports:create
      summary: A record's custody report
      description: Every ledger event about the record from filing to date, in order, with actors, reasons, times and each entry's place in the hash chain, plus a fresh integrity check. Also needs `folio:audit:read`. An audited read, ledgered as `record.custody_reported`; no reason.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/FolioId"
      responses:
        "200":
          description: The custody report.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioCustody"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/records/{id}/integrity:
    get:
      tags:
        - folio
      operationId: checkFolioIntegrity
      x-permission: folio:exports:create
      summary: Check a record's integrity now
      description: Reads the stored bytes and compares their SHA-256 with the receipt (`ok`, `mismatch` or `unreadable`). A mismatch raises `folio.integrity.alert`. A clean result means the bytes are the ones filed; it does not say the document is genuine.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/FolioId"
      responses:
        "200":
          description: The result.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioIntegrityResult"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/exports:
    post:
      tags:
        - folio
      operationId: createFolioExport
      x-permission: folio:exports:create
      summary: Request an export
      description: "`records` bundles each record's document, receipt, `record.json`, attachments, a hash manifest and (with `folio:audit:read`) its custody history; `custody`, `integrity` and `ledger` (needs `folio:audit:read`) are reports. A `records` export reads documents, so it also needs `folio:documents:read`, is a view of each one and takes a reason like opening a record (`428` without one where the rules require it). The scope (records, a folder, a matter or a subject) is expanded into its records first; a `records` export whose documents add up to more than 512 MB is refused (`400`). Built by a background job; `folio.export.ready` is sent when done. Only the requester can read or download it."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/FolioReasonId"
        - $ref: "#/components/parameters/FolioReasonCode"
        - $ref: "#/components/parameters/FolioReasonText"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioExportRequest"
      responses:
        "202":
          description: Requested.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioExport"
              example:
                id: exp_01J9X3M8V7K2Q4R5S6T7U8V9W0
                kind: records
                state: queued
                records: 1
                size: null
                sha256: null
                created_at_ms: 1790000000000
                ready_at_ms: null
                expires_at_ms: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "428":
          $ref: "#/components/responses/FolioReasonRequired"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/exports/{id}:
    get:
      tags:
        - folio
      operationId: getFolioExport
      x-permission: folio:exports:create
      summary: An export
      description: "`queued`, `running`, `ready` (downloadable until `expires_at_ms`), `failed` or `expired`. Only the person who requested it sees it (anyone else: `404`)."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/FolioId"
      responses:
        "200":
          description: The export.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioExport"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/exports/{id}/download:
    get:
      tags:
        - folio
      operationId: downloadFolioExport
      x-permission: folio:exports:create
      summary: Download an export
      description: "The bundle, a zip with `manifest.json`, streamed. Only for the person who requested it (anyone else: `404`), who must still hold `folio:documents:read` for a `records` export and `folio:audit:read` for a `ledger` or `custody` one (`403` otherwise). Ledgered (`export.downloaded`) before any byte is returned. `409` until it is ready, or once it has expired."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/FolioId"
      responses:
        "200":
          description: The bundle.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/zip:
              schema:
                type: string
                format: binary
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/records/{id}/link:
    post:
      tags:
        - folio
      operationId: createFolioLink
      x-permission: folio:documents:read
      summary: A short-lived download link
      description: A pre-signed URL for tools that cannot send a key or reason headers. Issuing it is a view (`428` without a reason where the rules require one), ledgered as `record.link_issued`; it lasts `seconds` (default 300, at most 900) and every download through it is ledgered as a view by you, before any byte is returned. The `url` is shown once.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/FolioId"
        - $ref: "#/components/parameters/FolioReasonId"
        - $ref: "#/components/parameters/FolioReasonCode"
        - $ref: "#/components/parameters/FolioReasonText"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioLinkRequest"
      responses:
        "201":
          description: The link.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioLink"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "428":
          $ref: "#/components/responses/FolioReasonRequired"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/links/{token}:
    get:
      tags:
        - folio
      operationId: downloadFolioLink
      x-permission: none
      summary: Download through a link
      description: "No key: the link is the credential. An unknown, tampered or expired link is `404`. `view=inline` shows a PDF, a PNG, JPEG, GIF or WebP image, or plain text in place, with a policy that runs no script and lets only the web app frame it; any other type is still a download. A browser's viewer reads a large document in parts with `Range` (one range, at most 64 MiB a time): the answer is `206` with `Content-Range`. A link's range requests are ledgered as one view, when the first of them serves a byte; each whole download is a view of its own. A range that starts past the end is `416`, and nothing is ledgered or counted. Every read counts the bytes it sends toward the download allowance."
      security: []
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - name: token
          in: path
          required: true
          schema:
            type: string
        - name: view
          in: query
          schema:
            type: string
            enum:
              - inline
        - $ref: "#/components/parameters/FolioRange"
        - $ref: "#/components/parameters/FolioIfRange"
      responses:
        "200":
          description: The document.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
            Accept-Ranges:
              $ref: "#/components/headers/FolioAcceptRanges"
            ETag:
              $ref: "#/components/headers/FolioETag"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "206":
          $ref: "#/components/responses/FolioPartialContent"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
        "416":
          $ref: "#/components/responses/FolioRangeNotSatisfiable"
        default:
          $ref: "#/components/responses/Internal"
  /folio/head-key:
    get:
      tags:
        - folio
      operationId: getFolioHeadKey
      x-permission: none
      summary: This stamp's published head key
      description: The public key this region's stamp signs ledger heads with, and its id. No key needed. A ledger names the keys that signed its heads (`head_keys` in `GET /folio/ledger`); check each against the published key of the stamp it names (this route on that region's host) before you trust it, then give them to the offline verifier. `404` where this stamp signs no heads. Limited per client address (429 `rate_limited`, with `Retry-After`).
      security: []
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
      responses:
        "200":
          description: The key.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                type: object
                required:
                  - region
                  - key_id
                  - public_key
                properties:
                  region:
                    type: string
                  key_id:
                    type: string
                  public_key:
                    type: string
                    description: PEM public key.
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/records/{id}/restore:
    post:
      tags:
        - folio
      operationId: restoreFolioRecord
      x-permission: folio:documents:read
      summary: Restore an archived record
      description: Asks for a readable copy of a record that has moved to Glacier Flexible Retrieval or Deep Archive, kept for `days` (7 by default, 1 to 30). `standard` takes 3 to 5 hours from Flexible Retrieval and 9 to 12 from Deep Archive; `bulk` takes 5 to 12 and 24 to 48. The price is the same at either speed. Authorised and ledgered as a view (`record.restore_requested`), so the organisation's reason rules apply, and metered once. `202` when it is asked for now; `200` with the open restore when there already is one (it is not asked for or charged again); `409 not_archived` when the record can be opened as it is. When it is ready the `folio.restore.ready` webhook is sent, and an email goes to `notify_email` when one is given.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/FolioId"
        - $ref: "#/components/parameters/FolioReasonId"
        - $ref: "#/components/parameters/FolioReasonCode"
        - $ref: "#/components/parameters/FolioReasonText"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioRestoreRequest"
      responses:
        "200":
          description: A restore was already open for this record; it is returned as it is.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioRestoreResult"
        "202":
          description: Asked for.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioRestoreResult"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "428":
          $ref: "#/components/responses/FolioReasonRequired"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
    get:
      tags:
        - folio
      operationId: getFolioRecordRestore
      x-permission: folio:records:read
      summary: Where a record is kept, and its restore
      description: "The record's storage (its tier, the storage class it is in, whether it needs a restore before it can be opened, the pin keeping it out of colder storage) and its open restore, or the latest one. Not a view: nothing of the document is shown."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/FolioId"
      responses:
        "200":
          description: Where it is kept.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                type: object
                required:
                  - restore
                  - storage
                properties:
                  restore:
                    oneOf:
                      - $ref: "#/components/schemas/FolioRestore"
                      - type: "null"
                  storage:
                    $ref: "#/components/schemas/FolioStorage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/restores:
    get:
      tags:
        - folio
      operationId: listFolioRestores
      x-permission: folio:records:read
      summary: List restores
      description: The organisation's restores, newest first. `open=true` keeps those asked for, running or ready.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: open
          in: query
          schema:
            type: boolean
            default: false
        - name: before
          in: query
          description: The `next` of the previous page.
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        "200":
          description: The restores.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                type: object
                required:
                  - restores
                  - next
                properties:
                  restores:
                    type: array
                    items:
                      $ref: "#/components/schemas/FolioRestore"
                  next:
                    type:
                      - string
                      - "null"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/pins:
    post:
      tags:
        - folio
      operationId: pinFolioStorage
      x-permission: folio:records:write
      summary: Keep a record or folder out of colder storage
      description: "Pins a record, or a folder and everything below it, so it stops moving to colder storage. Anything already in an archive class stays there and still needs a restore to open: a pin stops future moves, it does not bring anything back. Storage is then charged at the rate of the class each document is in. A change, so a reason is required; ledgered (`storage.pinned`). Pinning what is already pinned answers its active pin."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioPinRequest"
      responses:
        "201":
          description: Pinned.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioPin"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
    get:
      tags:
        - folio
      operationId: listFolioPins
      x-permission: folio:records:read
      summary: List pins
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: include_ended
          in: query
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: The pins, newest first.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                type: object
                required:
                  - pins
                properties:
                  pins:
                    type: array
                    items:
                      $ref: "#/components/schemas/FolioPin"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/pins/{id}/end:
    post:
      tags:
        - folio
      operationId: endFolioPin
      x-permission: folio:records:write
      summary: End a pin
      description: The pinned records take their storage profile's moves again, by their age. A change, so a reason is required; ledgered (`storage.unpinned`). Ending an ended pin answers it as it is.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioReasonOnly"
      responses:
        "200":
          description: Ended.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioPin"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/profiles:
    get:
      tags:
        - folio
      operationId: getFolioProfiles
      x-permission: folio:records:read
      summary: Storage profiles
      description: Each storage profile's moves to colder storage (at fixed ages, the same for every organisation), the size and retention below which a record stays warmer, the storage price, and the restore price, speeds and waits. Amounts are null until prices are set.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
      responses:
        "200":
          description: The profiles.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioProfiles"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/residency:
    get:
      tags:
        - folio
      operationId: getFolioResidency
      x-permission: folio:records:read
      summary: Where folio's data lives
      description: "The organisation's residency report, exportable for its auditors: its region and stamp, where each class of data lives, the keys and their regions, the flows that may leave the region (only hashes to the time-stamp authority, account metadata to the control plane, and counts and states to operations), each outbound service and whether it is pinned to the region, the personnel jurisdictions, the residency changes (organisation moves), recent denied attempts and when the controls were last tested. Facts only the infrastructure can attest are `null` until it does, and the report then says `complete: false`. An organisation whose residency is not this stamp's region is refused on every folio route (403). Ledgered as `residency.reported`."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
      responses:
        "200":
          description: The report.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioResidency"
              example:
                home_region: uk-london
                cloud_region: eu-west-2
                stamp: shared
                complete: false
                data:
                  - class: documents and their receipts
                    region: uk-london
                    enforced_by:
                      - service
                keys:
                  - purpose: the organisation's folio key (documents, second copy, derived data)
                    region: null
                    multi_region: null
                    enforced_by: infrastructure
                permitted_flows:
                  - what: hashes only (never content)
                    to: the time-stamp authority (uk)
                timestamp_jurisdiction: uk
                outbound:
                  - client: document store
                    carries: documents, attachments, queued files and exports
                    location: region
                    region: uk-london
                    pinned: true
                personnel_jurisdictions:
                  - GB
                residency_changes: []
                infrastructure:
                  account: null
                  region_lock: null
                  bucket_region: null
                  second_copy_region: null
                  key_region: null
                  multi_region_keys: null
                  cross_region_replication: null
                  egress_allow_list: null
                  recent_denials: []
                  last_tested_ms: null
                denied_attempts: []
                generated_at_ms: 1790000000000
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/imports:
    post:
      tags:
        - folio
      operationId: submitFolioImport
      x-permission: folio:records:write
      summary: Start an import, or add lines to it
      description: "A bulk import of an existing archive: send the manifest, then each line's file to `POST /folio/imports/{id}/content`. The body is a JSON manifest, or a CSV one (`Content-Type: text/csv`, a header row naming the columns `reference`, `folder`, `name`, `sha256`, `size`, `class`, `original_date`, `event_date`, `matter`, `subject`, with `reference` and `source_system` in the query). The import is named by its `reference`: the same manifest sent again, or in parts of up to 10,000 lines, adds only the lines it does not have, so an interrupted import is resumed by running it again. The answer carries the estimate of what is left to file, before any file is sent. A line repeated with another hash, or without a usable reference (1 to 200 characters), is listed in `rejected` and changes nothing. A line that cannot be filed (a bad name, an unknown class, a Locked class without the Locked add-on) is `refused` with why; a file the organisation has already filed, or an earlier line of the import carries, is a `duplicate` and is not filed again. Ledgered as `import.started`."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - name: reference
          in: query
          schema:
            type: string
          description: The import's reference, for a CSV manifest.
        - name: source_system
          in: query
          schema:
            type: string
          description: The system the archive comes from, for a CSV manifest.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioImportManifest"
          text/csv:
            schema:
              type: string
      responses:
        "200":
          description: The import, and what this call added.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioImportAccepted"
              example:
                import:
                  id: imp_01J9X3M8V7K2Q4R5S6T7U8V9W0
                  reference: legacy-2026
                  source_system: LegacyDMS
                  state: open
                  counts:
                    total: 2
                    awaiting: 2
                    queued: 0
                    filed: 0
                    duplicate: 0
                    refused: 0
                    failed: 0
                  bytes_expected: 36468
                  bytes_filed: 0
                  bytes_to_file: 36468
                  percent: 0
                  created_at_ms: 1790000000000
                  closed_at_ms: null
                  completed_at_ms: null
                added: 2
                unchanged: 0
                rejected: []
                estimate:
                  estimate:
                    files: 2
                    bytes: 36468
                    pages: 2
                    profile: reference
                    filing_minor: null
                    reading_minor: null
                    storage_month_minor: null
                    first_month_minor: null
                  priced: false
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
    get:
      tags:
        - folio
      operationId: listFolioImports
      x-permission: folio:records:read
      summary: The organisation's imports
      description: The latest 50 imports, newest first, each with its progress.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
      responses:
        "200":
          description: The imports.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                type: object
                required:
                  - imports
                properties:
                  imports:
                    type: array
                    items:
                      $ref: "#/components/schemas/FolioImport"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/imports/{id}:
    get:
      tags:
        - folio
      operationId: getFolioImport
      x-permission: folio:records:read
      summary: An import's progress
      description: Line counts by state, bytes declared, filed and still to file, the percentage finished, and the estimate of what is left to file (filing, reading at least a page a file, and a month of storage on the reference profile; amounts are null until prices are set). `open` takes lines and files; `closed` takes no more lines; `complete` once nothing is waiting or queued.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/FolioId"
      responses:
        "200":
          description: The import.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FolioImport"
                  - type: object
                    required:
                      - estimate
                    properties:
                      estimate:
                        $ref: "#/components/schemas/FolioImportCost"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/imports/{id}/content:
    post:
      tags:
        - folio
      operationId: sendFolioImportFile
      x-permission: folio:records:write
      summary: Send one line's file
      description: "The request body is the file of the manifest line named by `reference`. It must match the line's SHA-256 (otherwise 400, and nothing changes). It is then filed through the same pipeline as `POST /folio/records` (the same scan, rules, ledger and receipt), as bulk work, with the import's provenance and the line's original date. A line already sent is answered as it stands, so a resumed upload changes nothing. While the organisation has as much import work queued as it may, the answer is 429 `rate_limited`: send it again shortly."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/FolioId"
        - name: reference
          in: query
          required: true
          schema:
            type: string
          description: The manifest line's reference.
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "202":
          description: The line, `queued` (or a `duplicate`).
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioImportLine"
              example:
                seq: 1
                reference: dms/1
                folder: archive/2020
                name: letter.pdf
                sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
                size: 18234
                state: queued
                record_id: null
                duplicate_of: null
                error: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/imports/{id}/items:
    get:
      tags:
        - folio
      operationId: listFolioImportLines
      x-permission: folio:records:read
      summary: An import's lines
      description: "The lines in manifest order, optionally of one state: `state=refused` and `state=failed` are the per-line error report, each with why. Follow `next_after`. Ledgered as `import.listed`."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/FolioId"
        - name: state
          in: query
          schema:
            type: string
            enum:
              - awaiting
              - queued
              - filed
              - duplicate
              - refused
              - failed
        - name: after
          in: query
          schema:
            type: integer
            minimum: 0
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 1000
            default: 200
      responses:
        "200":
          description: A page of lines.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                type: object
                required:
                  - items
                  - next_after
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/FolioImportLine"
                  next_after:
                    type:
                      - integer
                      - "null"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/imports/{id}/close:
    post:
      tags:
        - folio
      operationId: closeFolioImport
      x-permission: folio:records:write
      summary: Close an import
      description: The import takes no more lines, and lines still waiting for their file are `failed`. It completes once its queued files are filed (ledgered as `import.closed`, then `import.completed` with the counts).
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/FolioId"
      responses:
        "200":
          description: The import.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioImport"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/imports/{id}/report:
    get:
      tags:
        - folio
      operationId: getFolioImportReport
      x-permission: folio:records:read
      summary: An import's report
      description: "The import's counts, every refused or failed line with why, and whether the filed lines reconcile with the ledger: one `record.imported` entry for each, and no other. Together with the hash manifest (`GET /folio/imports/{id}/manifest.csv`) it shows what was imported, when, from where and with which hashes. Ledgered as `import.reported`."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/FolioId"
      responses:
        "200":
          description: The report.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioImportReport"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/imports/{id}/manifest.csv:
    get:
      tags:
        - folio
      operationId: getFolioImportManifest
      x-permission: folio:records:read
      summary: An import's hash manifest
      description: "Every line as CSV: its line number, reference, folder, name, SHA-256, size, state, record, the line it duplicates and any error. Ledgered as `import.reported`."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - $ref: "#/components/parameters/FolioId"
      responses:
        "200":
          description: The hash manifest.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            text/csv:
              schema:
                type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/webhooks:
    get:
      tags:
        - folio
      operationId: listFolioWebhooks
      x-permission: folio:settings:manage
      summary: List folio's webhook endpoints
      description: folio's endpoints only; Nib's are listed by `GET /nib/webhooks`.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
      responses:
        "200":
          description: The endpoints.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioWebhookList"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
    post:
      tags:
        - folio
      operationId: createFolioWebhook
      x-permission: folio:settings:manage
      summary: Create a folio webhook endpoint
      description: "Like `POST /nib/webhooks` (the same signing, retries, delivery log and connection test), for folio's events: `folio.record.filed`, `folio.hold.applied`, `folio.hold.released`, `folio.record.disposed`, `folio.integrity.alert`, `folio.export.ready`, `folio.restore.ready`. The secret is shown once. No `Idempotency-Key`: a retry creates another endpoint."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioWebhookCreate"
      responses:
        "201":
          description: Created.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioWebhookCreated"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/webhooks/{webhook_id}:
    delete:
      tags:
        - folio
      operationId: deleteFolioWebhook
      x-permission: folio:settings:manage
      summary: Delete a folio webhook endpoint
      description: "`404` for an unknown endpoint and for a Nib endpoint."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: webhook_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/s3/keys:
    get:
      tags:
        - folio
      operationId: listFolioS3Keys
      x-permission: folio:settings:manage
      summary: List folio S3 keys
      description: "The access keys S3 clients use with folio S3 that can still be used: long-lived keys not revoked, and reason-session credentials not yet expired. Never a secret. Also names the organisation's bucket and region."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
      responses:
        "200":
          description: The keys.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioS3KeyList"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
    post:
      tags:
        - folio
      operationId: createFolioS3Key
      x-permission: folio:settings:manage
      summary: Create a folio S3 key
      description: "A long-lived access key for S3 clients (SigV4), acting as `s3_key:<id>`. Its permissions are folio permissions within yours. The secret is shown once. No `Idempotency-Key`: a retry creates another key. At most 20 keys at once (409)."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioS3KeyCreate"
      responses:
        "201":
          description: Created.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioS3KeyCreated"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/s3/keys/{id}:
    delete:
      tags:
        - folio
      operationId: revokeFolioS3Key
      x-permission: folio:settings:manage
      summary: Revoke a folio S3 key
      description: The key stops working at once. `404` for an unknown or already revoked key.
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Revoked.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
  /folio/s3/sessions:
    post:
      tags:
        - folio
      operationId: openFolioS3Session
      x-permission: folio:records:read
      summary: Open a reason session for S3 tools
      description: "For S3 tools that cannot send reason headers: opens a reason as `POST /folio/reasons` does (at most 20 minutes) and returns short-lived S3 credentials bound to it. They act as you, reach only the reason's context (a folder and its sub-folders, a matter, a subject or records), and stop when the reason ends. `permissions` default to your folio permissions. The secret is shown once; no `Idempotency-Key`."
      parameters:
        - $ref: "#/components/parameters/UnforgedVersion"
        - $ref: "#/components/parameters/UnforgedOrg"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FolioS3SessionCreate"
      responses:
        "201":
          description: Opened.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Unforged-Version:
              $ref: "#/components/headers/UnforgedVersion"
            Unforged-Latest-Version:
              $ref: "#/components/headers/UnforgedLatestVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FolioS3Session"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "421":
          $ref: "#/components/responses/WrongRegion"
        "429":
          $ref: "#/components/responses/RateLimited"
        default:
          $ref: "#/components/responses/Internal"
components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: unf_<env>_<opaque>
      description: An API key holding the route's permission.
    sessionToken:
      type: http
      scheme: bearer
      bearerFormat: unf_ses_<token>
      description: "A client session token: `GET /nib/live` and streamed `POST /nib/checks` only."
    userToken:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: A signed-in dashboard user's session token.
    internalToken:
      type: http
      scheme: bearer
      description: Reserved for Unforged.
    sessionSubprotocol:
      type: apiKey
      in: header
      name: Sec-WebSocket-Protocol
      description: "A session token as a WebSocket subprotocol: `unforged.session, <token>`."
  headers:
    RequestId:
      description: The request id; also in problem bodies.
      schema:
        type: string
        maxLength: 128
      example: req_01j9x3m8v7k2q4r5s6t7u8v9w0
    RetryAfter:
      description: Seconds to wait before retrying.
      schema:
        type: integer
        minimum: 0
      example: 1
    IdempotentReplayed:
      description: "`true` on a replayed response."
      schema:
        type: string
        const: "true"
    NoStore:
      description: The body holds a secret shown once.
      schema:
        type: string
        const: no-store
    WwwAuthenticate:
      schema:
        type: string
        const: Bearer
    UnforgedVersion:
      description: The API version used.
      schema:
        type: string
        pattern: ^\d{4}-\d{2}-\d{2}$
      example: 2026-09-27
    UnforgedLatestVersion:
      description: The latest API version.
      schema:
        type: string
        pattern: ^\d{4}-\d{2}-\d{2}$
      example: 2026-09-27
    FolioAcceptRanges:
      description: A document may be read in parts.
      schema:
        type: string
        const: bytes
    FolioETag:
      description: The document's SHA-256, quoted. A record never changes; name it in `If-Range`.
      schema:
        type: string
      example: '"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"'
    FolioContentRange:
      description: The part sent, and the document's size (`bytes */<size>` on a `416`).
      schema:
        type: string
      example: bytes 0-1048575/734003200
  parameters:
    FolioId:
      name: id
      in: path
      required: true
      description: A record, queued filing or export id.
      schema:
        type: string
      example: rec_01J9X3M8V7K2Q4R5S6T7U8V9W0
    FolioReasonId:
      name: Unforged-Reason-Id
      in: header
      required: false
      description: An open reason (`POST /folio/reasons`), when the organisation or the folder requires one for this view.
      schema:
        type: string
    FolioReasonCode:
      name: Unforged-Reason-Code
      in: header
      required: false
      description: "Instead of an open reason: a reason code for this one call (one of your organisation's codes). It is opened for exactly the records the call reads and ledgered like any reason."
      schema:
        type: string
      example: internal_review
    FolioReasonText:
      name: Unforged-Reason
      in: header
      required: false
      description: The reason's text, with `Unforged-Reason-Code` (plain ASCII).
      schema:
        type: string
    UnforgedVersion:
      name: Unforged-Version
      in: header
      required: false
      description: API version (`YYYY-MM-DD`) for this request; defaults to your pinned version.
      schema:
        type: string
        pattern: ^\d{4}-\d{2}-\d{2}$
      example: 2026-09-27
    UnforgedOrg:
      name: Unforged-Org
      in: header
      required: false
      description: Act in a child organisation (its id). Needs `org:children:access`.
      schema:
        type: string
        pattern: ^[A-Za-z0-9_]{1,64}$
      example: org_01j9x3m8v7k2q4r5s6t7u8v9w0
    FolioRange:
      name: Range
      in: header
      required: false
      description: "One range of bytes: `bytes=<first>-<last>`, `bytes=<first>-` or `bytes=-<count>` (the last bytes). At most 64 MiB is sent at a time, cut from the end; `Content-Range` says what was sent. Any other form, or several ranges, is answered with the whole document."
      schema:
        type: string
      example: bytes=0-1048575
    FolioIfRange:
      name: If-Range
      required: false
      in: header
      description: The `ETag` of an earlier answer. Anything else, such as a date, is answered with the whole document.
      schema:
        type: string
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: 1–255 printable ASCII characters, kept 24 hours.
      schema:
        type: string
        minLength: 1
        maxLength: 255
      example: inv-2291
  responses:
    FolioPartialContent:
      description: The part of the document a `Range` asked for.
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Unforged-Version:
          $ref: "#/components/headers/UnforgedVersion"
        Unforged-Latest-Version:
          $ref: "#/components/headers/UnforgedLatestVersion"
        Accept-Ranges:
          $ref: "#/components/headers/FolioAcceptRanges"
        ETag:
          $ref: "#/components/headers/FolioETag"
        Content-Range:
          $ref: "#/components/headers/FolioContentRange"
      content:
        application/octet-stream:
          schema:
            type: string
            format: binary
    FolioRangeNotSatisfiable:
      description: "`invalid_request`: the range starts past the end of the document. `Content-Range` gives its size."
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Unforged-Version:
          $ref: "#/components/headers/UnforgedVersion"
        Unforged-Latest-Version:
          $ref: "#/components/headers/UnforgedLatestVersion"
        Content-Range:
          $ref: "#/components/headers/FolioContentRange"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    FolioReasonRequired:
      description: "`reason_required`: a reason is missing, has expired, or is for another context."
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Unforged-Version:
          $ref: "#/components/headers/UnforgedVersion"
        Unforged-Latest-Version:
          $ref: "#/components/headers/UnforgedLatestVersion"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    WrongRegion:
      description: "`wrong_region`: call the `api_host` in the body."
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Unforged-Version:
          $ref: "#/components/headers/UnforgedVersion"
        Unforged-Latest-Version:
          $ref: "#/components/headers/UnforgedLatestVersion"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          example:
            type: https://unforged.sh/problems/wrong_region
            title: Wrong region
            status: 421
            detail: this organisation's data lives in uk-london; call https://uk-london.api.unforged.sh
            code: wrong_region
            request_id: req_01j9x3m8v7k2q4r5s6t7u8v9w0
            region: uk-london
            api_host: https://uk-london.api.unforged.sh
    Internal:
      description: "`internal`."
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Unforged-Version:
          $ref: "#/components/headers/UnforgedVersion"
        Unforged-Latest-Version:
          $ref: "#/components/headers/UnforgedLatestVersion"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    Conflict:
      description: "`conflict`, `idempotency_conflict`, `idempotency_in_progress` or `idempotency_not_replayable`."
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Unforged-Version:
          $ref: "#/components/headers/UnforgedVersion"
        Unforged-Latest-Version:
          $ref: "#/components/headers/UnforgedLatestVersion"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          example:
            type: https://unforged.sh/problems/idempotency_conflict
            title: Idempotency key reused with a different request
            status: 409
            detail: this Idempotency-Key was used with a different request
            code: idempotency_conflict
            request_id: req_01j9x3m8v7k2q4r5s6t7u8v9w0
    NotFound:
      description: "`not_found`."
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Unforged-Version:
          $ref: "#/components/headers/UnforgedVersion"
        Unforged-Latest-Version:
          $ref: "#/components/headers/UnforgedLatestVersion"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          example:
            type: https://unforged.sh/problems/not_found
            title: Not found
            status: 404
            detail: subject not found
            code: not_found
            request_id: req_01j9x3m8v7k2q4r5s6t7u8v9w0
    NotImplemented:
      description: "`not_implemented` or `not_available_in_dev`."
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Unforged-Version:
          $ref: "#/components/headers/UnforgedVersion"
        Unforged-Latest-Version:
          $ref: "#/components/headers/UnforgedLatestVersion"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    Unauthorized:
      description: "`unauthorized`: missing, invalid or expired credential."
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Unforged-Version:
          $ref: "#/components/headers/UnforgedVersion"
        Unforged-Latest-Version:
          $ref: "#/components/headers/UnforgedLatestVersion"
        WWW-Authenticate:
          $ref: "#/components/headers/WwwAuthenticate"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          example:
            type: https://unforged.sh/problems/unauthorized
            title: Unauthorized
            status: 401
            detail: invalid or revoked API key
            code: unauthorized
            request_id: req_01j9x3m8v7k2q4r5s6t7u8v9w0
    Unavailable:
      description: "`busy`, `not_ready`, `org_moving` or `storage_provisioning` (your organisation's storage is still being set up); retry after `Retry-After`."
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Unforged-Version:
          $ref: "#/components/headers/UnforgedVersion"
        Unforged-Latest-Version:
          $ref: "#/components/headers/UnforgedLatestVersion"
        Retry-After:
          $ref: "#/components/headers/RetryAfter"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          example:
            type: https://unforged.sh/problems/busy
            title: Service busy
            status: 503
            detail: no capacity became free in time; the check was accepted as job inv-2291
            code: busy
            request_id: req_01j9x3m8v7k2q4r5s6t7u8v9w0
            job_id: inv-2291
            status_url: /nib/checks/inv-2291
    Forbidden:
      description: "`permission_denied`, `org_suspended`, or `address_not_allowed` (your organisation only accepts requests from the addresses it has listed)."
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Unforged-Version:
          $ref: "#/components/headers/UnforgedVersion"
        Unforged-Latest-Version:
          $ref: "#/components/headers/UnforgedLatestVersion"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          example:
            type: https://unforged.sh/problems/permission_denied
            title: Forbidden
            status: 403
            detail: requires nib:settings:manage
            code: permission_denied
            request_id: req_01j9x3m8v7k2q4r5s6t7u8v9w0
    TooLarge:
      description: "`too_large`."
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Unforged-Version:
          $ref: "#/components/headers/UnforgedVersion"
        Unforged-Latest-Version:
          $ref: "#/components/headers/UnforgedLatestVersion"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    RateLimited:
      description: "`rate_limited` or `quota_exceeded`."
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Unforged-Version:
          $ref: "#/components/headers/UnforgedVersion"
        Unforged-Latest-Version:
          $ref: "#/components/headers/UnforgedLatestVersion"
        Retry-After:
          $ref: "#/components/headers/RetryAfter"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          example:
            type: https://unforged.sh/problems/rate_limited
            title: Rate limit exceeded
            status: 429
            detail: too many requests; retry after 1 s
            code: rate_limited
            request_id: req_01j9x3m8v7k2q4r5s6t7u8v9w0
    BadRequest:
      description: "`invalid_request`, `invalid_version`, `idempotency_key_required` or `invalid_signature`."
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Unforged-Version:
          $ref: "#/components/headers/UnforgedVersion"
        Unforged-Latest-Version:
          $ref: "#/components/headers/UnforgedLatestVersion"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          example:
            type: https://unforged.sh/problems/invalid_request
            title: Invalid request
            status: 400
            detail: subject_reference must be a valid id ([A-Za-z0-9._-]{1,128})
            code: invalid_request
            request_id: req_01j9x3m8v7k2q4r5s6t7u8v9w0
  schemas:
    FolioReceipt:
      type: object
      description: The receipt for a filed document.
      required:
        - record_id
        - sha256
      additionalProperties: true
      properties:
        record_id:
          type: string
        sha256:
          type: string
        size:
          type: integer
        class_id:
          type: string
        earliest_delete:
          type:
            - string
            - "null"
        lock:
          type: string
    FolioRecord:
      type: object
      required:
        - id
        - folder
        - name
        - size
        - sha256
        - class_id
        - lock
        - state
      additionalProperties: true
      properties:
        id:
          type: string
        folder:
          type: string
        name:
          type: string
        size:
          type: integer
        sha256:
          type: string
        class_id:
          type: string
        class_version:
          type: integer
        lock:
          type: string
          description: "The retention lock: `retained` or `locked`."
        earliest_delete:
          type:
            - string
            - "null"
        state:
          type: string
        matter:
          type:
            - string
            - "null"
        subject:
          type:
            - string
            - "null"
        metadata: {}
        receipt: {}
        filed_by:
          type: string
        filed_at_ms:
          type:
            - integer
            - "null"
        attachments:
          type: array
          description: Evidence filed with the document, each `{kind, sha256, body}`; a Nib result is `nib_result`.
          items:
            type: object
        nib_verdict:
          type: string
          description: The Nib decision filed with the record, or `no verdict recorded`.
        storage:
          description: Where it is kept (UNF-132); null when that could not be worked out.
          oneOf:
            - $ref: "#/components/schemas/FolioStorage"
            - type: "null"
    FolioRecordPage:
      type: object
      required:
        - records
        - next_after
      properties:
        records:
          type: array
          items:
            type: object
            required:
              - id
              - folder
              - name
              - size
              - class_id
              - lock
            additionalProperties: true
            properties:
              id:
                type: string
              folder:
                type: string
              name:
                type: string
              size:
                type: integer
              class_id:
                type: string
              lock:
                type: string
              earliest_delete:
                type:
                  - string
                  - "null"
              filed_at_ms:
                type:
                  - integer
                  - "null"
              archived:
                type: boolean
                description: In the archive now, so it needs a restore before it can be opened (UNF-132).
        next_after:
          type:
            - string
            - "null"
    FolioReasonRequest:
      type: object
      required:
        - code
        - context
      properties:
        code:
          type: string
          description: One of the organisation's reason codes.
        text:
          type:
            - string
            - "null"
        matter:
          type:
            - string
            - "null"
        context:
          type: object
          required:
            - kind
            - value
          properties:
            kind:
              type: string
              enum:
                - folder
                - matter
                - subject
                - records
            value: {}
        seconds:
          type: integer
          description: Requested lifetime; capped by the organisation and at 20 minutes.
    FolioReason:
      type: object
      required:
        - id
        - code
        - context
        - actor
        - opened_at_ms
        - expires_at_ms
      properties:
        id:
          type: string
        code:
          type: string
        text:
          type:
            - string
            - "null"
        matter:
          type:
            - string
            - "null"
        context: {}
        actor:
          type: string
        opened_at_ms:
          type: integer
        expires_at_ms:
          type: integer
    FolioLedgerPage:
      type: object
      required:
        - entries
        - next_before
        - head_keys
      properties:
        head_keys:
          type: array
          items:
            type: object
            required:
              - key_id
              - public_key
              - region
            properties:
              key_id:
                type: string
              public_key:
                type: string
                description: PEM public key.
              region:
                type: string
        entries:
          type: array
          items:
            type: object
            required:
              - seq
              - at_ms
              - actor
              - action
              - target
              - via
              - rule
              - hash
            additionalProperties: true
            properties:
              seq:
                type: integer
              at_ms:
                type: integer
              actor:
                type: string
              action:
                type: string
              target:
                type: string
              reason_id:
                type:
                  - string
                  - "null"
              reason_code:
                type:
                  - string
                  - "null"
              reason_text:
                type:
                  - string
                  - "null"
              via:
                type: string
              rule:
                type: string
              hash:
                type: string
              prev:
                type: string
                description: The hash of the event before it.
              request_id:
                type:
                  - string
                  - "null"
              head:
                type:
                  - object
                  - "null"
                description: The earliest signed head that covers the event; null until one has been signed.
                properties:
                  seq:
                    type: integer
                  at_ms:
                    type: integer
                  key_id:
                    type: string
                  stamped:
                    type: boolean
                    description: It also carries a trusted time-stamp.
              detail: {}
        next_before:
          type:
            - integer
            - "null"
    FolioSearchResult:
      type: object
      required:
        - scope
        - records
      properties:
        scope:
          type: string
          enum:
            - names
            - content
          description: What was searched.
        records:
          type: array
          description: "Record listings. In a content search each also has `pages` (matching pages, best first), `snippets` (null without folio:documents:read; otherwise `[{page, parts: [{text, hit}]}]`) and `low_confidence`."
          items:
            type: object
            additionalProperties: true
        matched_records:
          type: integer
          description: Content scope only.
        matched_pages:
          type: integer
          description: Content scope only.
        not_searchable:
          type: integer
          description: Content scope only. Records in scope whose text is not extracted yet, or failed.
        low_confidence:
          type: integer
          description: Content scope only. Records in scope whose text was read with low confidence.
        snippets:
          type: boolean
          description: Content scope only. Whether snippets were shown.
    FolioRestoreRequest:
      type: object
      additionalProperties: false
      properties:
        speed:
          type: string
          enum:
            - standard
            - bulk
          default: standard
        days:
          type: integer
          minimum: 1
          maximum: 30
          default: 7
          description: How long the restored copy can be read.
        notify_email:
          type:
            - string
            - "null"
          maxLength: 320
          description: Who to email when it is ready.
    FolioRestoreResult:
      type: object
      required:
        - restore
        - created
      properties:
        restore:
          $ref: "#/components/schemas/FolioRestore"
        created:
          type: boolean
          description: "false: a restore was already open and is returned as it is."
    FolioRestore:
      type: object
      required:
        - id
        - record_id
        - state
        - speed
        - days
        - storage_class
        - requested_by
        - asked_at_ms
        - estimate
      additionalProperties: true
      properties:
        id:
          type: string
        record_id:
          type: string
        state:
          type: string
          enum:
            - requested
            - in_progress
            - ready
            - expired
            - failed
        speed:
          type: string
          enum:
            - standard
            - bulk
        days:
          type: integer
        storage_class:
          type: string
          enum:
            - glacier_flexible
            - deep_archive
        requested_by:
          type: string
        asked_at_ms:
          type: integer
        expected_ready_at_ms:
          type:
            - integer
            - "null"
          description: "While it runs: the latest it should be ready by."
        ready_at_ms:
          type:
            - integer
            - "null"
        expires_at_ms:
          type:
            - integer
            - "null"
          description: Until when the restored copy can be read.
        estimate:
          type: object
          description: The estimate when it was asked for (as `GET /folio/estimates/restore`).
    FolioStorage:
      type: object
      required:
        - tier
        - storage_class
        - readable_now
        - archived
        - pin_id
        - restore
      additionalProperties: true
      properties:
        tier:
          type:
            - string
            - "null"
          enum:
            - active
            - reference
            - archive
            - deep-archive
            - null
          description: "null: filed before storage tiers and not yet tagged."
        storage_class:
          type: string
          enum:
            - standard
            - glacier_instant
            - glacier_flexible
            - deep_archive
        readable_now:
          type: boolean
          description: Whether the document can be opened now.
        archived:
          type: boolean
          description: Whether it is in a class that needs a restore before it can be opened.
        pin_id:
          type:
            - string
            - "null"
        restore:
          oneOf:
            - $ref: "#/components/schemas/FolioRestore"
            - type: "null"
    FolioPinRequest:
      type: object
      required:
        - kind
        - value
        - code
      additionalProperties: false
      properties:
        kind:
          type: string
          enum:
            - record
            - folder
        value:
          type: string
          maxLength: 1024
          description: The record id, or the folder (everything below it is covered too).
        code:
          type: string
        text:
          type:
            - string
            - "null"
    FolioPin:
      type: object
      required:
        - id
        - scope_kind
        - scope_value
        - reason_code
        - pinned_by
        - pinned_at_ms
      additionalProperties: true
      properties:
        id:
          type: string
        scope_kind:
          type: string
          enum:
            - record
            - folder
        scope_value:
          type: string
        reason_code:
          type: string
        reason_text:
          type:
            - string
            - "null"
        pinned_by:
          type: string
        pinned_at_ms:
          type: integer
        ended_at_ms:
          type:
            - integer
            - "null"
        ended_by:
          type:
            - string
            - "null"
    FolioProfiles:
      type: object
      required:
        - profiles
        - restore
        - priced
      properties:
        profiles:
          type: array
          items:
            type: object
            required:
              - profile
              - tier
              - steps
            additionalProperties: true
            properties:
              profile:
                type: string
                enum:
                  - active
                  - reference
                  - archive
                  - deep_archive
              tier:
                type: string
                enum:
                  - active
                  - reference
                  - archive
                  - deep-archive
              steps:
                type: array
                items:
                  type: object
                  additionalProperties: true
                  properties:
                    after_days:
                      type: integer
                    storage_class:
                      type: string
                    needs_restore:
                      type: boolean
                    minimum_days:
                      type: integer
                      description: The shortest time a document is charged for once it is in this class.
                    restore_hours:
                      type: object
                      description: "By speed: `{min_hours, max_hours}`."
              minimum_bytes:
                type:
                  - integer
                  - "null"
                description: Smaller documents stay in Standard.
              minimum_retention_days:
                type:
                  - integer
                  - "null"
                description: Documents kept for less stay warmer.
              storage_gb_month_micro:
                type:
                  - integer
                  - "null"
                description: Per GB-month, in millionths of a penny.
        restore:
          type: object
          additionalProperties: true
          properties:
            speeds:
              type: array
              items:
                type: string
            price_minor:
              type:
                - integer
                - "null"
              description: Per document, at either speed.
            days:
              type: object
        priced:
          type: boolean
    FolioReasonOnly:
      type: object
      required:
        - code
      properties:
        code:
          type: string
        text:
          type:
            - string
            - "null"
    FolioHoldRequest:
      type: object
      required:
        - scope
        - code
      properties:
        scope:
          type: object
          required:
            - kind
            - value
          properties:
            kind:
              type: string
              enum:
                - record
                - folder
                - matter
                - subject
            value:
              type: string
        code:
          type: string
        text:
          type:
            - string
            - "null"
    FolioHold:
      type: object
      required:
        - id
        - scope
        - reason_code
        - placed_by
        - placed_at_ms
      additionalProperties: true
      properties:
        id:
          type: string
        scope: {}
        reason_code:
          type: string
        reason_text:
          type:
            - string
            - "null"
        placed_by:
          type: string
        placed_at_ms:
          type: integer
        released_at_ms:
          type:
            - integer
            - "null"
        released_by:
          type:
            - string
            - "null"
        records_covered:
          type: integer
    FolioFolderRequest:
      type: object
      required:
        - folder
        - code
      properties:
        folder:
          type: string
        code:
          type: string
        text:
          type:
            - string
            - "null"
    FolioFolderRename:
      type: object
      required:
        - path
        - name
        - code
      properties:
        path:
          type: string
          description: The folder to rename.
        name:
          type: string
          description: Its new last segment
          with no slash.: null
        code:
          type: string
          description: The reason code.
        text:
          type:
            - string
            - "null"
          description: The reason's details (at least 10 characters).
    FolioFolderRenamed:
      type: object
      required:
        - from
        - to
        - records
      properties:
        from:
          type: string
        to:
          type: string
        records:
          type: integer
          description: Documents whose folder changed.
    FolioFolderProtected:
      type: object
      required:
        - folder
        - protected
      additionalProperties: false
      properties:
        folder:
          type: string
        protected:
          type: boolean
    FolioMove:
      type: object
      required:
        - record_id
        - from
        - to
      additionalProperties: false
      properties:
        record_id:
          type: string
        from:
          type: string
        to:
          type: string
    FolioApprovalList:
      type: object
      required:
        - approvals
        - next_before
      additionalProperties: false
      properties:
        approvals:
          type: array
          items:
            allOf:
              - $ref: "#/components/schemas/FolioApproval"
              - type: object
                required:
                  - mine
                properties:
                  mine:
                    type: boolean
                    description: The caller made this request.
        next_before:
          type:
            - string
            - "null"
    FolioApproval:
      type: object
      required:
        - id
        - action
        - requested_by
        - state
      additionalProperties: true
      properties:
        id:
          type: string
        action:
          type: string
          enum:
            - release_hold
            - early_delete
            - apply_class
            - erase_subject
            - config_change
            - unprotect_folder
            - move_record
            - class_change
            - rename_folder
        target:
          description: "What the request would do (for a configuration change: the configuration before and after, and what it reduces)."
        requested_by:
          type: string
        request_code:
          type: string
          description: The requester's reason code.
        request_text:
          type:
            - string
            - "null"
        requested_at_ms:
          type: integer
        expires_at_ms:
          type: integer
        state:
          type: string
          enum:
            - pending
            - done
            - refused
        approved_by:
          type:
            - string
            - "null"
          description: "Who decided it: approved it or, when `refused`, refused it."
        result:
          description: "What the approval did, where it reports (an erasure: the records erased, and those kept with the reason)."
        decided_at_ms:
          type:
            - integer
            - "null"
          description: When it was approved or refused.
    FolioRetentionRequest:
      type: object
      required:
        - earliest_delete
        - code
      properties:
        earliest_delete:
          type: string
          format: date
        code:
          type: string
        text:
          type:
            - string
            - "null"
    FolioClass:
      type: object
      required:
        - id
        - name
        - period
        - start
        - lock
        - profile
        - end_action
        - version
      additionalProperties: true
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        period:
          description: 'For example {"unit": "years", "n": 7}, or {"unit": "no_end"}.'
        start:
          type: string
          enum:
            - filing
            - event
            - supplied
        lock:
          type: string
          enum:
            - retained
            - locked
        profile:
          type: string
          enum:
            - active
            - reference
            - archive
            - deep_archive
        end_action:
          description: 'For example {"action": "delete_after_notice", "notice_days": 30} or {"action": "ask_first"}.'
        version:
          type: integer
    FolioClassRequest:
      type: object
      required:
        - id
        - name
        - period
        - start
        - lock
        - profile
        - end_action
        - reason_code
      properties:
        id:
          type: string
          description: a-z, 0-9, hyphen and underscore, up to 63 characters.
        name:
          type: string
        description:
          type: string
        period: {}
        start:
          type: string
          enum:
            - filing
            - event
            - supplied
        lock:
          type: string
          enum:
            - retained
            - locked
        profile:
          type: string
          enum:
            - active
            - reference
            - archive
            - deep_archive
        end_action: {}
        reason_code:
          type: string
        reason_text:
          type:
            - string
            - "null"
    FolioExtensionPreview:
      type: object
      required:
        - class_id
        - to_version
        - records
        - bytes
      properties:
        class_id:
          type: string
        to_version:
          type: integer
        records:
          type: integer
        bytes:
          type: integer
        earliest_new_end:
          type:
            - string
            - "null"
          format: date
        latest_new_end:
          type:
            - string
            - "null"
          format: date
    FolioErasureRequest:
      type: object
      required:
        - subject
        - code
      properties:
        subject:
          type: string
        include_retained:
          type: boolean
          default: false
        code:
          type: string
        text:
          type:
            - string
            - "null"
    FolioErasureAssessment:
      type: object
      required:
        - subject
        - items
      properties:
        subject:
          type: string
        items:
          type: array
          items:
            type: object
            required:
              - record_id
              - standing
              - holds
            additionalProperties: true
            properties:
              record_id:
                type: string
              standing:
                type: string
                enum:
                  - due
                  - retained
                  - locked
                  - held
              earliest_delete:
                type:
                  - string
                  - "null"
              explanation:
                type:
                  - string
                  - "null"
              holds:
                type: array
                items:
                  type: object
                  properties:
                    hold:
                      type: string
                    reason_code:
                      type: string
                    reason_text:
                      type:
                        - string
                        - "null"
    FolioQueuedFiling:
      type: object
      required:
        - id
        - state
        - folder
        - name
        - size
        - sha256
        - record_id
        - error
        - created_at_ms
        - finished_at_ms
      additionalProperties: true
      properties:
        id:
          type: string
        state:
          type: string
          enum:
            - queued
            - filed
            - refused
            - failed
        folder:
          type: string
        name:
          type: string
        size:
          type: integer
        sha256:
          type: string
        record_id:
          type:
            - string
            - "null"
        error:
          type:
            - string
            - "null"
        created_at_ms:
          type: integer
        finished_at_ms:
          type:
            - integer
            - "null"
    FolioUploadStart:
      type: object
      required:
        - name
        - size
      additionalProperties: false
      properties:
        folder:
          type: string
          default: ""
        name:
          type: string
        class:
          type: string
          description: The retention class; the organisation's default when omitted.
        matter:
          type: string
        subject:
          type: string
        source_system:
          type: string
        source_ref:
          type: string
        original_date:
          type: string
          format: date
        size:
          type: integer
          minimum: 1
          maximum: 2147483648
    FolioUploadStarted:
      type: object
      required:
        - id
        - state
        - size
        - part_size
        - part_count
        - max_parts_in_flight
      additionalProperties: true
      properties:
        id:
          type: string
        state:
          type: string
          enum:
            - uploading
        size:
          type: integer
        part_size:
          type: integer
        part_count:
          type: integer
        max_parts_in_flight:
          type: integer
          description: How many parts to send at once.
    FolioUploadSignRequest:
      type: object
      required:
        - parts
      additionalProperties: false
      properties:
        parts:
          type: array
          minItems: 1
          maxItems: 8
          items:
            type: object
            required:
              - number
              - size
              - sha256
            additionalProperties: false
            properties:
              number:
                type: integer
                minimum: 1
              size:
                type: integer
                minimum: 1
              sha256:
                type: string
                description: The SHA-256 of the part's bytes, base64.
    FolioUploadSigned:
      type: object
      required:
        - parts
      additionalProperties: true
      properties:
        parts:
          type: array
          items:
            type: object
            required:
              - number
              - method
              - url
              - headers
              - expires_at_ms
            additionalProperties: true
            properties:
              number:
                type: integer
              method:
                type: string
              url:
                type: string
              headers:
                type: object
                additionalProperties:
                  type: string
              expires_at_ms:
                type: integer
    FolioUploadCompleteRequest:
      type: object
      required:
        - parts
      additionalProperties: false
      properties:
        parts:
          type: array
          items:
            type: object
            required:
              - number
              - sha256
            additionalProperties: false
            properties:
              number:
                type: integer
                minimum: 1
              sha256:
                type: string
    FolioUpload:
      type: object
      required:
        - id
        - state
        - folder
        - name
        - size
        - part_size
        - part_count
        - parts
        - bytes_received
        - record_id
        - error
        - created_at_ms
        - updated_at_ms
      additionalProperties: true
      properties:
        id:
          type: string
        state:
          type: string
          enum:
            - uploading
            - checking
            - filed
            - refused
            - failed
            - aborted
        folder:
          type: string
        name:
          type: string
        size:
          type: integer
        part_size:
          type: integer
        part_count:
          type: integer
        parts:
          type: array
          description: The parts that have arrived, while `uploading` (from `GET /folio/uploads/{id}` only).
          items:
            type: object
            required:
              - number
              - size
              - sha256
            properties:
              number:
                type: integer
              size:
                type: integer
              sha256:
                type:
                  - string
                  - "null"
                description: "The SHA-256 storage checked the part against (base64): a part that is not your file's is sent again."
        bytes_received:
          type: integer
        record_id:
          type:
            - string
            - "null"
        error:
          type:
            - string
            - "null"
        created_at_ms:
          type: integer
        updated_at_ms:
          type: integer
    FolioUploadList:
      type: object
      required:
        - items
        - intake_origin
      additionalProperties: true
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/FolioUpload"
        intake_origin:
          type:
            - string
            - "null"
          description: The origin every part's URL names (`https://<bucket>.s3.<region>.amazonaws.com`), for a browser's content security policy.
    FolioFromCheck:
      type: object
      required:
        - job_id
        - item_index
      properties:
        job_id:
          type: string
        item_index:
          type: integer
          minimum: 0
        folder:
          type: string
        name:
          type: string
          description: The file name in folio; the checked file's own name when omitted.
        class:
          type: string
        matter:
          type: string
        subject:
          type: string
      example:
        job_id: job-2291
        item_index: 0
        folder: cases/2291
    FolioFolder:
      type: object
      required:
        - path
        - matter
        - subject
        - protected
        - created_at_ms
      additionalProperties: true
      properties:
        path:
          type: string
        matter:
          type:
            - string
            - "null"
        subject:
          type:
            - string
            - "null"
        protected:
          type: boolean
        created_at_ms:
          type: integer
    FolioFolderPage:
      type: object
      required:
        - folders
        - next_after
      properties:
        folders:
          type: array
          items:
            $ref: "#/components/schemas/FolioFolder"
        next_after:
          type:
            - string
            - "null"
    FolioFolderCreate:
      type: object
      required:
        - path
      properties:
        path:
          type: string
        matter:
          type: string
        subject:
          type: string
      example:
        path: clients/smith
        matter: M-1042
    FolioFolderDetail:
      allOf:
        - $ref: "#/components/schemas/FolioFolder"
        - type: object
          required:
            - records
            - folders
          properties:
            records:
              type: integer
            folders:
              type: integer
    FolioIntegrity:
      type: object
      required:
        - record_id
        - expected
        - found
        - result
        - checked_at_ms
      properties:
        record_id:
          type: string
        expected:
          type: string
        found:
          type:
            - string
            - "null"
        result:
          type: string
          enum:
            - ok
            - mismatch
            - unreadable
        checked_at_ms:
          type: integer
    FolioIntegrityResult:
      type: object
      required:
        - integrity
        - note
      properties:
        integrity:
          $ref: "#/components/schemas/FolioIntegrity"
        note:
          type: string
    FolioCustody:
      type: object
      required:
        - record_id
        - folder
        - name
        - sha256
        - events
        - integrity
        - note
      additionalProperties: true
      properties:
        record_id:
          type: string
        folder:
          type: string
        name:
          type: string
        sha256:
          type: string
        events:
          type: array
          items:
            type: object
            required:
              - seq
              - at_ms
              - actor
              - action
              - target
              - prev
              - hash
            additionalProperties: true
            properties:
              seq:
                type: integer
              at_ms:
                type: integer
              actor:
                type: string
              action:
                type: string
              target:
                type: string
              reason_code:
                type:
                  - string
                  - "null"
              reason_text:
                type:
                  - string
                  - "null"
              prev:
                type: string
              hash:
                type: string
        integrity:
          $ref: "#/components/schemas/FolioIntegrity"
        note:
          type: string
    FolioExportRequest:
      type: object
      required:
        - kind
      properties:
        kind:
          type: string
          enum:
            - records
            - custody
            - integrity
            - ledger
        records:
          type: array
          maxItems: 1000
          items:
            type: string
        folder:
          type: string
        recursive:
          type: boolean
          default: false
        matter:
          type: string
        subject:
          type: string
      example:
        kind: records
        records:
          - rec_01J9X3M8V7K2Q4R5S6T7U8V9W0
    FolioExport:
      type: object
      required:
        - id
        - kind
        - state
        - records
        - size
        - sha256
        - created_at_ms
        - ready_at_ms
        - expires_at_ms
      additionalProperties: true
      properties:
        id:
          type: string
        kind:
          type: string
          enum:
            - records
            - custody
            - integrity
            - ledger
        state:
          type: string
          enum:
            - queued
            - running
            - ready
            - failed
            - expired
        records:
          type: integer
        size:
          type:
            - integer
            - "null"
        sha256:
          type:
            - string
            - "null"
        created_at_ms:
          type: integer
        ready_at_ms:
          type:
            - integer
            - "null"
        expires_at_ms:
          type:
            - integer
            - "null"
    FolioLinkRequest:
      type: object
      properties:
        seconds:
          type: integer
          minimum: 1
          maximum: 900
          default: 300
      example:
        seconds: 120
    FolioLink:
      type: object
      required:
        - id
        - record_id
        - url
        - expires_at_ms
      properties:
        id:
          type: string
        record_id:
          type: string
        url:
          type: string
          description: Relative to the API base URL; shown once.
        expires_at_ms:
          type: integer
    FolioResidencyDenial:
      type: object
      required:
        - client
        - target_region
        - at_ms
        - observed_by
      properties:
        client:
          type: string
        target_region:
          type: string
        at_ms:
          type: integer
        observed_by:
          type: string
          enum:
            - service
            - infrastructure
    FolioResidency:
      type: object
      required:
        - home_region
        - cloud_region
        - stamp
        - complete
        - data
        - keys
        - permitted_flows
        - timestamp_jurisdiction
        - outbound
        - personnel_jurisdictions
        - residency_changes
        - infrastructure
        - denied_attempts
        - generated_at_ms
      additionalProperties: true
      properties:
        home_region:
          type: string
        cloud_region:
          type: string
        stamp:
          type: string
          enum:
            - shared
            - dedicated
        complete:
          type: boolean
          description: Whether every fact is attested
          the infrastructure's included.: null
        data:
          type: array
          items:
            type: object
            required:
              - class
              - region
              - enforced_by
            properties:
              class:
                type: string
              region:
                type: string
              enforced_by:
                type: array
                items:
                  type: string
                  enum:
                    - service
                    - infrastructure
        keys:
          type: array
          items:
            type: object
            required:
              - purpose
              - region
              - multi_region
              - enforced_by
            properties:
              purpose:
                type: string
              region:
                type:
                  - string
                  - "null"
              multi_region:
                type:
                  - boolean
                  - "null"
              enforced_by:
                type: string
        permitted_flows:
          type: array
          items:
            type: object
            required:
              - what
              - to
            properties:
              what:
                type: string
              to:
                type: string
        timestamp_jurisdiction:
          type: string
          enum:
            - uk
            - eu
            - us
        outbound:
          type: array
          items:
            type: object
            required:
              - client
              - carries
              - location
              - region
              - pinned
            properties:
              client:
                type: string
              carries:
                type: string
              location:
                type: string
                enum:
                  - region
                  - this_host
                  - jurisdiction
              region:
                type:
                  - string
                  - "null"
              pinned:
                type: boolean
        personnel_jurisdictions:
          type: array
          items:
            type: string
        residency_changes:
          type: array
          items:
            type: object
            required:
              - from
              - to
              - at_ms
              - records
            properties:
              from:
                type: string
              to:
                type: string
              at_ms:
                type: integer
              records:
                type: integer
        infrastructure:
          type: object
          required:
            - account
            - region_lock
            - bucket_region
            - second_copy_region
            - key_region
            - multi_region_keys
            - cross_region_replication
            - egress_allow_list
            - recent_denials
            - last_tested_ms
          properties:
            account:
              type:
                - string
                - "null"
            region_lock:
              type:
                - array
                - "null"
              items:
                type: string
            bucket_region:
              type:
                - string
                - "null"
            second_copy_region:
              type:
                - string
                - "null"
            key_region:
              type:
                - string
                - "null"
            multi_region_keys:
              type:
                - boolean
                - "null"
            cross_region_replication:
              type:
                - boolean
                - "null"
            egress_allow_list:
              type:
                - boolean
                - "null"
            recent_denials:
              type: array
              items:
                $ref: "#/components/schemas/FolioResidencyDenial"
            last_tested_ms:
              type:
                - integer
                - "null"
        denied_attempts:
          type: array
          items:
            $ref: "#/components/schemas/FolioResidencyDenial"
        generated_at_ms:
          type: integer
    FolioImportManifestLine:
      type: object
      required:
        - reference
        - name
        - sha256
      additionalProperties: false
      properties:
        reference:
          type: string
          description: The source system's id for the file, or its relative path; unique within the import.
        folder:
          type: string
        name:
          type: string
        sha256:
          type: string
          description: Hex SHA-256 of the file.
        size:
          type:
            - integer
            - "null"
        class:
          type:
            - string
            - "null"
        original_date:
          type:
            - string
            - "null"
          format: date
          description: The original created or event date; retention starts from it for classes that count from an event.
        event_date:
          type:
            - string
            - "null"
          format: date
        matter:
          type:
            - string
            - "null"
        subject:
          type:
            - string
            - "null"
        metadata:
          type:
            - object
            - "null"
    FolioImportManifest:
      type: object
      required:
        - reference
        - source_system
        - items
      additionalProperties: false
      properties:
        reference:
          type: string
          description: "The import's name, chosen by you: the same reference resumes the same import."
        source_system:
          type: string
        items:
          type: array
          maxItems: 10000
          items:
            $ref: "#/components/schemas/FolioImportManifestLine"
      example:
        reference: legacy-2026
        source_system: LegacyDMS
        items:
          - reference: dms/1
            folder: archive/2020
            name: letter.pdf
            sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
            size: 18234
            original_date: 2020-01-15
    FolioImportCost:
      type: object
      required:
        - estimate
        - priced
      properties:
        estimate:
          type: object
          required:
            - files
            - bytes
            - pages
            - profile
            - filing_minor
            - reading_minor
            - storage_month_minor
            - first_month_minor
          properties:
            files:
              type: integer
            bytes:
              type: integer
            pages:
              type: integer
            profile:
              type: string
            filing_minor:
              type:
                - integer
                - "null"
            reading_minor:
              type:
                - integer
                - "null"
            storage_month_minor:
              type:
                - integer
                - "null"
            first_month_minor:
              type:
                - integer
                - "null"
        priced:
          type: boolean
          description: Whether any price is set (amounts are null until they are).
    FolioImportCounts:
      type: object
      required:
        - total
        - awaiting
        - queued
        - filed
        - duplicate
        - refused
        - failed
      properties:
        total:
          type: integer
        awaiting:
          type: integer
        queued:
          type: integer
        filed:
          type: integer
        duplicate:
          type: integer
        refused:
          type: integer
        failed:
          type: integer
    FolioImport:
      type: object
      required:
        - id
        - reference
        - source_system
        - state
        - counts
        - bytes_expected
        - bytes_filed
        - bytes_to_file
        - percent
        - created_at_ms
        - closed_at_ms
        - completed_at_ms
      additionalProperties: true
      properties:
        id:
          type: string
        reference:
          type: string
        source_system:
          type: string
        state:
          type: string
          enum:
            - open
            - closed
            - complete
        counts:
          $ref: "#/components/schemas/FolioImportCounts"
        bytes_expected:
          type: integer
        bytes_filed:
          type: integer
        bytes_to_file:
          type: integer
          description: The declared bytes of the lines waiting for their file or queued.
        percent:
          type: integer
          minimum: 0
          maximum: 100
        created_at_ms:
          type: integer
        closed_at_ms:
          type:
            - integer
            - "null"
        completed_at_ms:
          type:
            - integer
            - "null"
    FolioImportLine:
      type: object
      required:
        - seq
        - reference
        - folder
        - name
        - sha256
        - size
        - state
        - record_id
        - duplicate_of
        - error
      additionalProperties: true
      properties:
        seq:
          type: integer
        reference:
          type: string
        folder:
          type: string
        name:
          type: string
        sha256:
          type: string
        size:
          type:
            - integer
            - "null"
        state:
          type: string
          enum:
            - awaiting
            - queued
            - filed
            - duplicate
            - refused
            - failed
        record_id:
          type:
            - string
            - "null"
        duplicate_of:
          type:
            - string
            - "null"
        error:
          type:
            - string
            - "null"
    FolioImportAccepted:
      type: object
      required:
        - import
        - added
        - unchanged
        - rejected
        - estimate
      additionalProperties: true
      properties:
        import:
          $ref: "#/components/schemas/FolioImport"
        estimate:
          $ref: "#/components/schemas/FolioImportCost"
        added:
          type: integer
        unchanged:
          type: integer
        rejected:
          type: array
          items:
            type: object
            required:
              - line
              - reference
              - error
            properties:
              line:
                type: integer
              reference:
                type: string
              error:
                type: string
    FolioImportReport:
      type: object
      required:
        - import
        - failures
        - ledger_imported
        - reconciled
      additionalProperties: true
      properties:
        import:
          $ref: "#/components/schemas/FolioImport"
        failures:
          type: array
          items:
            $ref: "#/components/schemas/FolioImportLine"
        ledger_imported:
          type: integer
        reconciled:
          type: boolean
    FolioWebhook:
      type: object
      required:
        - id
        - url
        - events
        - created_at
      properties:
        id:
          type: string
        url:
          type: string
        events:
          type: array
          items:
            type: string
        created_at:
          type: string
    FolioWebhookList:
      type: object
      required:
        - webhooks
      properties:
        webhooks:
          type: array
          items:
            $ref: "#/components/schemas/FolioWebhook"
    FolioWebhookCreate:
      type: object
      required:
        - url
        - events
      additionalProperties: false
      properties:
        url:
          type: string
        events:
          type: array
          minItems: 1
          items:
            type: string
            enum:
              - folio.record.filed
              - folio.hold.applied
              - folio.hold.released
              - folio.record.disposed
              - folio.integrity.alert
              - folio.export.ready
              - folio.restore.ready
      example:
        url: https://hooks.example.com/folio
        events:
          - folio.record.filed
    FolioWebhookCreated:
      allOf:
        - $ref: "#/components/schemas/FolioWebhook"
        - type: object
          required:
            - secret
          properties:
            secret:
              type: string
              description: Shown once.
    FolioS3Key:
      type: object
      required:
        - id
        - access_key_id
        - permissions
        - actor
        - created_by
        - created_at_ms
      properties:
        id:
          type: string
        access_key_id:
          type: string
        name:
          type:
            - string
            - "null"
        permissions:
          type: array
          items:
            type: string
        actor:
          type: string
          description: Who requests made with it are recorded as.
        reason_id:
          type:
            - string
            - "null"
          description: A reason session's reason.
        created_by:
          type: string
        created_at_ms:
          type: integer
        expires_at_ms:
          type:
            - integer
            - "null"
        failed_sign_ins:
          type: integer
          description: Sign-ins refused with this key (a wrong signature or clock), up to a million.
        last_failed_sign_in_at_ms:
          type:
            - integer
            - "null"
    FolioS3KeyList:
      type: object
      required:
        - keys
        - bucket
        - region
      properties:
        keys:
          type: array
          items:
            $ref: "#/components/schemas/FolioS3Key"
        bucket:
          type: string
          description: The organisation's bucket, `folio-<slug>`.
        region:
          type: string
    FolioS3KeyCreate:
      type: object
      required:
        - permissions
      additionalProperties: false
      properties:
        name:
          type:
            - string
            - "null"
        permissions:
          type: array
          items:
            type: string
    FolioS3KeyCreated:
      allOf:
        - $ref: "#/components/schemas/FolioS3Key"
        - type: object
          required:
            - secret_access_key
            - bucket
            - region
          properties:
            secret_access_key:
              type: string
              description: Shown once.
            bucket:
              type: string
            region:
              type: string
    FolioS3SessionCreate:
      allOf:
        - $ref: "#/components/schemas/FolioReasonRequest"
        - type: object
          properties:
            permissions:
              type:
                - array
                - "null"
              items:
                type: string
    FolioS3Session:
      allOf:
        - $ref: "#/components/schemas/FolioS3KeyCreated"
        - type: object
          required:
            - reason
          properties:
            reason:
              $ref: "#/components/schemas/FolioReason"
    Problem:
      type: object
      description: An RFC 9457 problem document.
      required:
        - type
        - title
        - status
        - detail
        - code
      properties:
        type:
          type: string
          description: "`https://unforged.sh/problems/<code>`"
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        code:
          type: string
          enum:
            - invalid_request
            - unsupported_media
            - too_large
            - fetch_failed
            - rate_limited
            - quota_exceeded
            - idempotency_conflict
            - internal
            - not_found
            - unauthorized
            - permission_denied
            - timeout
            - not_available_in_dev
            - method_not_allowed
            - conflict
            - busy
            - not_ready
            - idempotency_in_progress
            - idempotency_key_required
            - idempotency_not_replayable
            - not_implemented
            - not_allowlisted
            - tenant_not_provisioned
            - billing_required
            - invalid_signature
            - billing_already_active
            - invalid_version
            - org_suspended
            - wrong_region
            - org_moving
            - learning_terms_required
            - webhook_test_failed
            - awaiting_first_payment
            - reason_required
            - access_suspended
            - storage_provisioning
            - address_not_allowed
          description: A stable error code.
        request_id:
          type: string
        region:
          type: string
          description: On `wrong_region`, your home region.
        api_host:
          type: string
          description: On `wrong_region`, that region's API host.
        address:
          type: string
          description: On `address_not_allowed`, the address your request came from.
        terms_version:
          type: string
          description: On `learning_terms_required`, the version to accept.
        job_id:
          type: string
          description: On `busy` or `timeout` of a check that continues.
        status_url:
          type: string
        host:
          type: string
          description: On a failed URL fetch, the host.
        reason:
          type: string
          description: On a failed URL fetch, why (e.g. `http_status`, `timeout`). On `org_suspended`, `idle` when the organisation was removed for inactivity (it can start again).
        checks:
          type: object
          description: On `not_ready`.
        errors:
          type: array
          description: On a synchronous check whose job failed.
      examples:
        - type: https://unforged.sh/problems/not_found
          title: Not found
          status: 404
          detail: job not found
          code: not_found
          request_id: req_01j9x3m8v7k2q4r5s6t7u8v9w0
        - type: https://unforged.sh/problems/timeout
          title: Request timed out
          status: 504
          detail: the item exceeded its 30 s budget; the check continues asynchronously
          code: timeout
          request_id: req_01j9x3m8v7k2q4r5s6t7u8v9w0
          job_id: inv-2291
          status_url: /nib/checks/inv-2291
