{
  "openapi": "3.1.0",
  "info": {
    "title": "Sellat API",
    "version": "2",
    "description": "Everything the web app does, from your own system: register a file’s SHA-256 fingerprint, anchor it on Polygon and Bitcoin, request the FNMT qualified timestamp, download the certificate and the evidence package, and keep the original in custody. REST and JSON, with an API key.",
    "contact": {
      "name": "Sellat",
      "url": "https://sellat.app/developers",
      "email": "hola@sellat.app"
    }
  },
  "externalDocs": {
    "description": "Sellat API v2",
    "url": "https://sellat.app/en/developers/docs"
  },
  "servers": [
    {
      "url": "https://sellat.app/api/v2"
    }
  ],
  "tags": [
    {
      "name": "Proofs",
      "description": "Register a SHA-256 fingerprint and follow the proof: Merkle batch, Polygon anchor, Bitcoin attestation."
    },
    {
      "name": "Qualified seal (eIDAS)",
      "description": "The FNMT-RCM qualified timestamp (RFC 3161, eIDAS) over the fingerprint, and its token."
    },
    {
      "name": "Certificate and evidence",
      "description": "The human-readable certificate and the evidence package, for defending a proof without Sellat."
    },
    {
      "name": "Custody of the original",
      "description": "Keep the original next to its proof, in the EU; re-hashed on the way in and out."
    },
    {
      "name": "Account",
      "description": "What the calling key can do: plan, seals left, custody, daily limits."
    },
    {
      "name": "Portable proof",
      "description": "The portable proof (sellat-proof/2): public, verifiable without an account or Sellat."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Every request carries your key in the `Authorization: Bearer sellat_...` header. Keys are created and revoked from your dashboard; an account can hold up to 5 active keys, one per integration.\n\nWithout a key, or with a revoked one, the API answers `401 unauthorized` with a `WWW-Authenticate: Bearer` header. The only public route is the portable proof (`/proof/{id}.json`)."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ],
            "additionalProperties": true
          }
        },
        "required": [
          "error"
        ]
      },
      "PlainError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      }
    }
  },
  "paths": {
    "/proofs": {
      "post": {
        "operationId": "create-proof",
        "tags": [
          "Proofs"
        ],
        "summary": "Create a proof",
        "description": "Registers a file’s SHA-256 fingerprint and returns the proof. With `\"qualified\": true` it also issues the FNMT qualified seal in the same call, spending one seal from the account.\n\nIf the authority is slow to answer, the proof is still created and `qualified` comes back as `{\"state\": \"pending\"}`: ask again with `POST /proofs/{id}/qualified`, at no cost.\n\nIf another account registered those bytes first, the proof is created and anchored but has no certificate of its own: the certificate URLs are `null` and the response carries `\"limitations\": [\"hash_registered_by_another_account\"]`.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Retry without duplicating (see Idempotency).",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hash": {
                    "type": "string",
                    "pattern": "^[a-f0-9]{64}$",
                    "description": "The file’s SHA-256 fingerprint: 64 hexadecimal characters."
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "What the dashboard and the certificate call it. Only the file name is kept, not the path. Defaults to the first 12 characters of the hash."
                  },
                  "metadata": {
                    "type": "object",
                    "description": "A JSON object of yours, up to 4 KB. Stored with the proof, never returned or published."
                  },
                  "size": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "File size in bytes. Informational."
                  },
                  "mime_type": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "The file’s type, used when serving the original if you keep it in custody. Defaults to `application/octet-stream`."
                  },
                  "qualified": {
                    "type": "boolean",
                    "description": "`true` also issues the FNMT qualified seal."
                  }
                },
                "required": [
                  "hash"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "It already existed: these bytes are already your account’s, or you repeated an `Idempotency-Key`. Carries `\"created\": false`."
          },
          "201": {
            "description": "Proof created.",
            "content": {
              "application/json": {
                "example": {
                  "id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
                  "created": true,
                  "hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f",
                  "algorithm": "SHA-256",
                  "name": "contract.pdf",
                  "state": "queued",
                  "received_at": "2026-09-30T09:12:03.418Z",
                  "anchors": [],
                  "bitcoin": {
                    "state": "pending",
                    "block_height": null
                  },
                  "qualified": {
                    "state": "issued",
                    "authority": "FNMT-RCM",
                    "time": "2026-09-30T09:12:05.000Z",
                    "serial_number": "175755C77B4239226AA0428FE1C107C1",
                    "policy_oid": "0.4.0.2023.1.1",
                    "tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
                  },
                  "original": null,
                  "urls": {
                    "self": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
                    "proof_json": "https://sellat.app/api/v2/proof/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e.json",
                    "certificate": "https://sellat.app/certificate/3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b",
                    "certificate_pdf": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/certificate.pdf",
                    "evidence_zip": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/evidence.zip"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: a field is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "`payment_required`: you asked for the seal and have none left. Nothing is written.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`conflict`: you asked for the seal on bytes another account registered first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`quota_exceeded` (daily limit) or `rate_limited` (per minute).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`seal_unavailable` or `unavailable`. If the proof was created, the error carries its `proof_id`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "list-proofs",
        "tags": [
          "Proofs"
        ],
        "summary": "List proofs",
        "description": "Your account’s proofs, newest first: those created through the API and those created on the web. Each item in `data` is a full proof, like `GET /proofs/{id}`.\n\nFor the next page, pass the `next_cursor` the previous one returned. When it is `null`, there are no more.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Proofs per page, 1 to 100. Defaults to 25.",
            "schema": {
              "type": "integer",
              "maximum": 100,
              "minimum": 1
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `next_cursor` of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`data` and `next_cursor`.",
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
                      "hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f",
                      "name": "contract.pdf",
                      "state": "confirmed",
                      "received_at": "2026-09-30T09:12:03.418Z"
                    }
                  ],
                  "next_cursor": "1843"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: invalid `limit` or `cursor`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/proofs/{id}": {
      "get": {
        "operationId": "get-proof",
        "tags": [
          "Proofs"
        ],
        "summary": "Retrieve a proof",
        "description": "Returns the proof with its state, its anchors, the Bitcoin attestation, the seal if it has one, the original if it is in custody, and all its URLs.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The proof’s `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The proof.",
            "content": {
              "application/json": {
                "example": {
                  "id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
                  "hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f",
                  "algorithm": "SHA-256",
                  "name": "contract.pdf",
                  "state": "anchored",
                  "received_at": "2026-09-30T09:12:03.418Z",
                  "anchors": [
                    {
                      "network": "Polygon Mainnet",
                      "chain_id": 137,
                      "state": "anchored",
                      "tx_hash": "0xa4195d4ea808610dee92a1caa221d35f289e61674f451c82f7428e29813f9d6a",
                      "block_number": 94653624,
                      "block_timestamp": "2026-09-30T09:14:36.000Z",
                      "explorer_url": "https://polygonscan.com/tx/0xa4195d4ea808610dee92a1caa221d35f289e61674f451c82f7428e29813f9d6a"
                    }
                  ],
                  "bitcoin": {
                    "state": "pending",
                    "block_height": null
                  },
                  "qualified": {
                    "state": "issued",
                    "authority": "FNMT-RCM",
                    "time": "2026-09-30T09:12:05.000Z",
                    "serial_number": "175755C77B4239226AA0428FE1C107C1",
                    "policy_oid": "0.4.0.2023.1.1",
                    "tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
                  },
                  "original": null,
                  "urls": {
                    "self": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
                    "proof_json": "https://sellat.app/api/v2/proof/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e.json",
                    "certificate": "https://sellat.app/certificate/3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b",
                    "certificate_pdf": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/certificate.pdf",
                    "evidence_zip": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/evidence.zip"
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`: it does not exist or belongs to another account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/proofs/{id}/qualified": {
      "post": {
        "operationId": "request-seal",
        "tags": [
          "Qualified seal (eIDAS)"
        ],
        "summary": "Request the qualified seal",
        "description": "Issues an RFC 3161 timestamp from FNMT-RCM over the file’s fingerprint. Under Article 41(2) of the eIDAS Regulation, a qualified electronic timestamp enjoys the presumption of the accuracy of the date and time it indicates and of the integrity of the data.\n\nIt spends one seal from the account: the welcome seal or one from your packs. The API never charges a card or opens a payment; packs are bought on the pricing page.\n\nA proof has one seal, forever. If issuance fails for good, the seal goes back to your balance.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The proof’s `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Seal issued, or it already had one (`\"created\": false`, nothing spent). Carries `seals_remaining`.",
            "content": {
              "application/json": {
                "example": {
                  "proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
                  "created": true,
                  "qualified": {
                    "state": "issued",
                    "authority": "FNMT-RCM",
                    "time": "2026-09-30T09:12:05.000Z",
                    "serial_number": "175755C77B4239226AA0428FE1C107C1",
                    "policy_oid": "0.4.0.2023.1.1",
                    "tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
                  },
                  "seals_remaining": 4
                }
              }
            }
          },
          "202": {
            "description": "The seal is paid for but the authority did not answer. Call again (`Retry-After: 30`): it completes at no cost."
          },
          "401": {
            "description": "`unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "`payment_required`: no seals left. Carries `seals_remaining` and `buy_url`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`conflict`: another account registered these bytes first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`seal_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "get-seal",
        "tags": [
          "Qualified seal (eIDAS)"
        ],
        "summary": "Retrieve the seal",
        "description": "Returns only the proof’s seal, or `null` if it has none yet. It issues nothing and spends nothing.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The proof’s `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`proof_id` and `qualified`.",
            "content": {
              "application/json": {
                "example": {
                  "proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
                  "qualified": {
                    "state": "issued",
                    "authority": "FNMT-RCM",
                    "time": "2026-09-30T09:12:05.000Z",
                    "serial_number": "175755C77B4239226AA0428FE1C107C1",
                    "policy_oid": "0.4.0.2023.1.1",
                    "tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/proofs/{id}/qualified.tsr": {
      "get": {
        "operationId": "download-token",
        "tags": [
          "Qualified seal (eIDAS)"
        ],
        "summary": "Download the token (.tsr)",
        "description": "The seal exactly as the authority returned it: an RFC 3161 TimeStampResp in DER. It is the file an expert or a court can validate without Sellat.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The proof’s `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The `.tsr` (`application/timestamp-reply`).",
            "content": {
              "application/timestamp-reply": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`: the proof does not exist or has no seal yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/proofs/{id}/certificate.pdf": {
      "get": {
        "operationId": "certificate-pdf",
        "tags": [
          "Certificate and evidence"
        ],
        "summary": "PDF certificate",
        "description": "The proof’s certificate, for people: the fingerprint, the date, the anchors and the qualified seal if there is one, with instructions to verify it.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The proof’s `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Certificate language: `es`, `en`, `de` or `fr`. Any other value gives `en`.",
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en",
                "de",
                "fr"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The PDF.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`conflict`: another account registered these bytes first and the proof has no certificate of its own.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/proofs/{id}/evidence.zip": {
      "get": {
        "operationId": "evidence-zip",
        "tags": [
          "Certificate and evidence"
        ],
        "summary": "Evidence package",
        "description": "A ZIP with what it takes to defend the proof without Sellat: the certificate, `proof.json`, the seal and the authority’s certificate if there is one, the instructions and, if it is in custody, the original.\n\nIncluding the original spends one of the day’s downloads. If none are left, the package comes without it and the README says so.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The proof’s `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Language of the package’s texts: `es`, `en`, `de` or `fr`.",
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en",
                "de",
                "fr"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The ZIP.",
            "content": {
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`conflict`: another account registered these bytes first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/proofs/{id}/original": {
      "put": {
        "operationId": "deposit-original",
        "tags": [
          "Custody of the original"
        ],
        "summary": "Deposit the original",
        "description": "Stores the file next to its proof, on servers in the European Union. Sellat recomputes the fingerprint of what it receives and refuses it if it does not match the proof.\n\nSend the raw bytes with the file’s `Content-Type`, or `multipart/form-data` under the `file` field. `POST` works the same as `PUT`.\n\nCustody is optional: the proof is just as valid without the original. Keeping it adds preservation; it does not condition the proof.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The proof’s `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The file. Up to 10 MB on the free account and 50 MB on the Pro account, and 25 MB per request at most.",
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                },
                "required": [
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "It was already stored."
          },
          "201": {
            "description": "Stored. Returns the proof with `original`.",
            "content": {
              "application/json": {
                "example": {
                  "id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
                  "state": "anchored",
                  "original": {
                    "stored": true,
                    "file_name": "contract.pdf",
                    "size_bytes": 184320,
                    "mime_type": "application/pdf",
                    "stored_at": "2026-09-30T09:15:10.000Z",
                    "url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/original"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: the body is empty or unreadable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`hash_mismatch` (not the proof’s file) or `conflict` (another account’s bytes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`file_too_large`: over your plan’s per-file limit. Carries `max_file_bytes`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "`unavailable`: storage did not answer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`custody_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "507": {
            "description": "`custody_quota_exceeded`: no space left on your account. Carries `used_bytes` and `max_account_bytes`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "download-original",
        "tags": [
          "Custody of the original"
        ],
        "summary": "Download the original",
        "description": "Returns the file in custody. Sellat recomputes its fingerprint on the way out and refuses to serve it if it no longer matches. Each download counts towards your plan’s daily limit; the `X-Sellat-Custody-Downloads-Remaining` header says how many are left.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The proof’s `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The file, with its type and name.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No original in custody.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`conflict`: the original is not stored yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`download_quota_exceeded`: daily download limit. Resets at 00:00 UTC.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`integrity_failed`: what is stored no longer matches the fingerprint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "`unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "delete-original",
        "tags": [
          "Custody of the original"
        ],
        "summary": "End custody",
        "description": "Deletes the original in custody. The proof does not change: it stays anchored, sealed and verifiable.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The proof’s `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`\"original\": null` and `\"proof_unaffected\": true`. `\"purged\": false` means physical deletion is queued.",
            "content": {
              "application/json": {
                "example": {
                  "proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
                  "original": null,
                  "purged": true,
                  "proof_unaffected": true
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No original in custody.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/account": {
      "get": {
        "operationId": "get-account",
        "tags": [
          "Account"
        ],
        "summary": "Your account",
        "description": "What the calling key can do: the plan, the seals you have left (welcome and packs), custody space and downloads, today’s proofs and their limit, and the active keys. Check it before a large batch.\n\nThe `connector` block holds the limits for connected shops (WooCommerce, coming soon).",
        "responses": {
          "200": {
            "description": "The account.",
            "content": {
              "application/json": {
                "example": {
                  "plan": "free",
                  "plan_name": "Free",
                  "seals": {
                    "available": true,
                    "remaining": 1,
                    "welcome_remaining": 1,
                    "pack_remaining": 0,
                    "price_eur": 6,
                    "buy_url": "https://sellat.app/precios"
                  },
                  "custody": {
                    "available": true,
                    "used_bytes": 0,
                    "max_account_bytes": 52428800,
                    "max_file_bytes": 10485760,
                    "downloads_used_today": 0,
                    "max_downloads_per_day": 10
                  },
                  "proofs": {
                    "daily_limit": 10,
                    "used_today": 3,
                    "resets_in_seconds": 41231
                  },
                  "connector": {
                    "mode_available": "daily",
                    "eidas_available": false,
                    "custody_available": false,
                    "operations": {
                      "daily_limit": 2000,
                      "resets_in_seconds": 41231
                    }
                  },
                  "keys": {
                    "active": 1
                  },
                  "urls": {
                    "dashboard": "https://sellat.app/dashboard",
                    "plans": "https://sellat.app/precios#pricing-business",
                    "seals": "https://sellat.app/precios#pricing-seal-title",
                    "docs": "https://sellat.app/developers/docs",
                    "connect": "https://sellat.app/connect/woocommerce"
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/proof/{id}.json": {
      "get": {
        "operationId": "portable-proof",
        "tags": [
          "Portable proof"
        ],
        "summary": "Portable proof (proof.json)",
        "description": "The document that lets anyone verify the proof without Sellat, in the public `sellat-proof/2` format: the fingerprint, the leaf, the Merkle path, the root, the Polygon anchor and the attestations (Bitcoin and the qualified seal). It is public on purpose: whoever holds it can check the proof without an account.",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The proof’s `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The `proof.json`.",
            "content": {
              "application/json": {
                "example": {
                  "schema": "sellat-proof/2",
                  "proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
                  "content": {
                    "algorithm": "SHA-256",
                    "hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f"
                  },
                  "leaf": {
                    "formula": "SHA-256('sellat-leaf:v2:' + proof_id + ':' + content.hash)",
                    "value": "31668b4d846680cd920a1cb5be200ab0562909e8778223087c1738746a1cbb26"
                  },
                  "merkle": {
                    "index": 1,
                    "path": [
                      {
                        "position": "left",
                        "hash": "9e246a6d6dda91ee3916e149d834cd9a140e3470fbf5b6ec67824705dbbc82d4"
                      }
                    ],
                    "root": "88fa1c4c3587d86d6f713e438c7ae745a91a6e03a5dd1b8282f17f35f30bfa14"
                  },
                  "anchors": [
                    {
                      "chain_id": 137,
                      "network": "Polygon Mainnet",
                      "tx_hash": "0xa4195d4ea808610dee92a1caa221d35f289e61674f451c82f7428e29813f9d6a",
                      "block_number": 94653624,
                      "payload": "sellat:v2:88fa1c4c3587d86d6f713e438c7ae745a91a6e03a5dd1b8282f17f35f30bfa14"
                    }
                  ],
                  "attestations": [
                    {
                      "type": "opentimestamps",
                      "state": "submitted"
                    },
                    {
                      "type": "rfc3161-qualified-timestamp",
                      "state": "issued"
                    }
                  ]
                }
              }
            }
          },
          "202": {
            "description": "Not anchored yet: `{\"ready\": false, \"state\": \"...\"}` with `Retry-After: 60`."
          },
          "400": {
            "description": "The `id` is not a UUID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlainError"
                }
              }
            }
          },
          "404": {
            "description": "It does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlainError"
                }
              }
            }
          }
        }
      }
    }
  }
}