{
  "openapi": "3.1.0",
  "info": {
    "title": "Flovvy Receipts API",
    "version": "1",
    "summary": "A photo of a receipt in, structured JSON out.",
    "description": "Send a photo of a receipt; get back merchant, date, totals and every line — with weight, unit and price per unit — plus a check that the lines add up to the printed total. Full guide: README.md next to this file."
  },
  "servers": [
    {
      "url": "https://phxkxiktoklomfmflvcw.supabase.co/functions/v1/b2b-api",
      "description": "Production (EU). Your base URL is also given to you with your keys."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Receipts",
      "description": "Send receipt photos and read the results."
    },
    {
      "name": "Account",
      "description": "Your credits."
    },
    {
      "name": "Service",
      "description": "Is the service up."
    },
    {
      "name": "Risk checks",
      "description": "On request: signals on a receipt you already sent (duplicates, payment match, edited or AI-generated images, totals). Separate base URL."
    }
  ],
  "paths": {
    "/v1/receipts": {
      "post": {
        "tags": [
          "Receipts"
        ],
        "operationId": "createReceipt",
        "summary": "Send a receipt photo",
        "description": "Live keys use 1 credit per receipt (returned if the receipt ends `failed`). Test keys are free and return a fixed example. Without `wait` the answer is 202 at once; with `wait` (1-30 s) the call waits for the result and answers 200 if it is ready in time.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReceiptRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The receipt is finished (`completed` or `failed`).",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Flovvy-Credits-Remaining": {
                "$ref": "#/components/headers/CreditsRemaining"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Receipt"
                }
              }
            }
          },
          "202": {
            "description": "Accepted: the receipt is `queued` or `processing`. Read it later at `Location`.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Flovvy-Credits-Remaining": {
                "$ref": "#/components/headers/CreditsRemaining"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "Location": {
                "$ref": "#/components/headers/Location"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Receipt"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: the body or a header is not valid.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: missing, malformed, unknown or revoked key.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "`insufficient_credits`: no credit left. Nothing was stored.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Flovvy-Credits-Remaining": {
                "$ref": "#/components/headers/CreditsRemaining"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: the account is suspended, or the key lacks `receipts:write`.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_conflict`: this Idempotency-Key was used with a different request.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "`image_too_large`: the image is over 8 MB.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "`unsupported_media_type`: only image/jpeg, image/png, image/webp.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable`: retry after the given seconds.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/receipts/{id}": {
      "get": {
        "tags": [
          "Receipts"
        ],
        "operationId": "getReceipt",
        "summary": "Read a receipt",
        "description": "Only the account and environment (live or test) that created the receipt can read it; for anyone else it does not exist (404). Receipts are deleted 90 days after they were sent (their photo after 7 days); then: 404.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^rcp_[0-9a-f]{32}$"
            },
            "example": "rcp_66f9b6e6f336492cb548530936316513"
          }
        ],
        "responses": {
          "200": {
            "description": "The receipt.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Receipt"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: the account is suspended, or the key lacks `receipts:read`.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`: no receipt with this id for this account and environment.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable`: busy for a moment; retry after the given seconds.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/receipts/{id}/checks": {
      "servers": [
        {
          "url": "https://phxkxiktoklomfmflvcw.supabase.co/functions/v1/b2b-antifrode",
          "description": "Risk checks (EU). A different base URL from the rest of the API."
        }
      ],
      "post": {
        "tags": [
          "Risk checks"
        ],
        "operationId": "checkReceipt",
        "summary": "Run the risk checks on a receipt",
        "description": "Signals on a receipt that was read (`completed`): duplicates among your receipts sent before it (90 days), the match with a payment (`expected`), the image file (editing software, declared AI generation or editing, photo date), the arithmetic. No credits, no AI, read-only: call it as often as you like. Signals are for a person to review, never a verdict. Full guide: CHECKS.md.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^rcp_[0-9a-f]{32}$"
            },
            "example": "rcp_66f9b6e6f336492cb548530936316513"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChecksRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The checks.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReceiptChecks"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: the body is not JSON, has an unknown field, or a value out of range.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: the account is suspended, or the key lacks `receipts:read`.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`: no receipt with this id for this account and environment.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "`receipt_not_completed`: the receipt is still queued or processing, or it failed.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable`: busy for a moment; retry after the given seconds.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/balance": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getBalance",
        "summary": "Read your credit balance",
        "responses": {
          "200": {
            "description": "The balance.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "`forbidden`: the account is suspended, or the key lacks `usage:read`.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable`: busy for a moment; retry after the given seconds.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/status": {
      "get": {
        "tags": [
          "Service"
        ],
        "operationId": "getStatus",
        "summary": "Is the service up?",
        "security": [],
        "responses": {
          "200": {
            "description": "The service answers.",
            "headers": {
              "Flovvy-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status"
                  ],
                  "properties": {
                    "status": {
                      "const": "ok"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "flv_live_… or flv_test_… (52 characters)",
        "description": "Your API key. Live keys (`flv_live_`) read real receipts and use credits; test keys (`flv_test_`) are free and return a fixed example."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Any value you choose, 1-255 printable ASCII characters, no spaces at the start or end (for example your own document id). For 24 hours, the same key with the same request returns the same receipt (no new reading, no new credit); with a different request: 409.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255,
          "pattern": "^[\\x21-\\x7E](?:[\\x20-\\x7E]{0,253}[\\x21-\\x7E])?$"
        }
      }
    },
    "headers": {
      "RequestId": {
        "description": "The id of this request. Quote it when you contact support.",
        "schema": {
          "type": "string",
          "pattern": "^req_[0-9a-f]{24}$"
        }
      },
      "CreditsRemaining": {
        "description": "Live keys only: your credit balance right after this receipt reserved its credit (or your balance, on a 402).",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "IdempotentReplayed": {
        "description": "`true` when this answer is a replay of an earlier request with the same Idempotency-Key.",
        "schema": {
          "type": "string",
          "const": "true"
        }
      },
      "Location": {
        "description": "Where to read the receipt, relative to the base URL: `/v1/receipts/{id}`.",
        "schema": {
          "type": "string"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "schemas": {
      "ReceiptRequest": {
        "type": "object",
        "required": [
          "image_base64"
        ],
        "properties": {
          "image_base64": {
            "type": "string",
            "contentEncoding": "base64",
            "description": "The photo, base64-encoded (no `data:` prefix). Up to 8 MB once decoded. Recommended: longest side 2,000 px, JPEG quality 85."
          },
          "mime": {
            "enum": [
              "image/jpeg",
              "image/png",
              "image/webp"
            ],
            "default": "image/jpeg"
          },
          "client_reference": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "Your own reference; returned as is in the receipt."
          },
          "wait": {
            "type": "integer",
            "minimum": 0,
            "maximum": 30,
            "default": 0,
            "description": "Seconds to wait for the result in this same call."
          }
        }
      },
      "Receipt": {
        "title": "Receipt",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "object",
          "status",
          "environment",
          "client_reference",
          "created_at",
          "completed_at",
          "schema_version",
          "pipeline_version",
          "merchant",
          "category",
          "issued_on",
          "currency",
          "totals",
          "lines",
          "reconciled",
          "reconciliation",
          "failure",
          "warnings"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^rcp_[0-9a-f]{32}$"
          },
          "object": {
            "const": "receipt"
          },
          "status": {
            "enum": [
              "queued",
              "processing",
              "completed",
              "failed"
            ]
          },
          "environment": {
            "enum": [
              "live",
              "test"
            ]
          },
          "client_reference": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 128
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "schema_version": {
            "const": "2026-10-15"
          },
          "pipeline_version": {
            "type": [
              "string",
              "null"
            ]
          },
          "merchant": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": false,
            "required": [
              "name",
              "country"
            ],
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "country": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^[A-Z]{2}$",
                "description": "ISO 3166-1 alpha-2, only when supported by printed clues (address, tax id, phone, language)."
              }
            }
          },
          "category": {
            "enum": [
              "groceries",
              "food_and_drink",
              "vehicle",
              "home",
              "utilities",
              "health",
              "shopping",
              "entertainment",
              "sport",
              "education",
              "travel",
              "technology",
              "subscriptions",
              "gifts",
              "beauty",
              "tobacco",
              "other",
              null
            ]
          },
          "issued_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[A-Z]{3}$"
          },
          "totals": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": false,
            "required": [
              "total",
              "total_source",
              "tax",
              "discounts",
              "net_of_tax"
            ],
            "properties": {
              "total": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "total_source": {
                "enum": [
                  "printed",
                  "sum_of_lines"
                ]
              },
              "tax": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "discounts": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "description": "Receipt-level discounts, always >= 0."
              },
              "net_of_tax": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          },
          "lines": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "n",
                "kind",
                "description",
                "quantity",
                "unit_price",
                "total",
                "weight",
                "unit",
                "price_per_unit",
                "applies_to"
              ],
              "properties": {
                "n": {
                  "type": "integer",
                  "minimum": 1,
                  "description": "Line number, 1-based, in printed order."
                },
                "kind": {
                  "enum": [
                    "item",
                    "discount"
                  ],
                  "description": "item = a purchased product; discount = a negative line."
                },
                "description": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Printed text of the line, never translated. null for discount lines (printed text not available yet)."
                },
                "quantity": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Number of pieces. null for discount lines."
                },
                "unit_price": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "total": {
                  "type": "number",
                  "description": "Line total as printed; negative for discounts."
                },
                "weight": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Weight or volume of one piece, in `unit`."
                },
                "unit": {
                  "enum": [
                    "kg",
                    "g",
                    "l",
                    "ml",
                    "lb",
                    "oz",
                    "m",
                    "ft",
                    null
                  ]
                },
                "price_per_unit": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Price per `unit` (e.g. per kg)."
                },
                "applies_to": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "minimum": 1,
                  "description": "For discounts: the `n` of the line the discount refers to, when known."
                }
              }
            }
          },
          "reconciled": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "true = lines, with printed tax and discounts, add up to the printed total exactly. It does not mean every line is right."
          },
          "reconciliation": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": false,
            "required": [
              "status",
              "delta",
              "tolerance",
              "basis"
            ],
            "properties": {
              "status": {
                "enum": [
                  "reconciled",
                  "not_reconciled",
                  "not_applicable"
                ]
              },
              "delta": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "tolerance": {
                "const": 0
              },
              "basis": {
                "enum": [
                  "lines",
                  "lines_plus_tax",
                  "lines_minus_discounts",
                  "lines_plus_tax_minus_discounts",
                  null
                ]
              }
            }
          },
          "failure": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": false,
            "required": [
              "code",
              "credit_refunded"
            ],
            "properties": {
              "code": {
                "enum": [
                  "unreadable",
                  "total_not_found",
                  "provider_unavailable",
                  "timeout",
                  "processing_error"
                ]
              },
              "credit_refunded": {
                "type": "boolean",
                "description": "true = the credit of this receipt came back to your balance. A failed reading we already paid our AI provider for is not refunded, unless it timed out. Always false in test mode (no credit is used)."
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "enum": [
                "issued_on_not_printed",
                "merchant_not_printed",
                "total_not_printed",
                "lines_not_itemized",
                "test_mode"
              ]
            }
          }
        }
      },
      "Balance": {
        "type": "object",
        "required": [
          "object",
          "credits",
          "plan_credits",
          "plan_credits_expire_at",
          "updated_at"
        ],
        "properties": {
          "object": {
            "const": "balance"
          },
          "credits": {
            "type": "integer",
            "minimum": 0,
            "description": "Credits left (monthly plan and packs together). 1 credit = 1 live receipt."
          },
          "plan_credits": {
            "type": "integer",
            "minimum": 0,
            "description": "The part of `credits` from the monthly plan, used first; 0 without a plan."
          },
          "plan_credits_expire_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "End of the paid month: unused plan credits expire at the next renewal, or at this time if the plan ends. null without plan credits."
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Last change of the balance; null if never topped up."
          }
        }
      },
      "ChecksRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "expected": {
            "type": "object",
            "additionalProperties": false,
            "minProperties": 1,
            "description": "The payment the receipt should justify (for example the card transaction). At least one of amount, date, merchant.",
            "properties": {
              "amount": {
                "type": "number",
                "exclusiveMinimum": 0,
                "description": "Amount paid."
              },
              "currency": {
                "type": "string",
                "pattern": "^[A-Za-z]{3}$",
                "description": "ISO 4217 code of `amount`; if it differs from the receipt currency the amounts are not compared."
              },
              "date": {
                "type": "string",
                "description": "Payment date: `YYYY-MM-DD` or an ISO 8601 date-time (only the date is used)."
              },
              "merchant": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200,
                "description": "Merchant name as you have it; a card statement descriptor is fine."
              }
            }
          },
          "date_tolerance_days": {
            "type": "integer",
            "minimum": 0,
            "maximum": 30,
            "default": 2
          },
          "amount_tolerance": {
            "type": "number",
            "minimum": 0,
            "maximum": 1000000,
            "default": 0,
            "description": "Accepted difference between the amounts (for tips)."
          }
        }
      },
      "ReceiptChecks": {
        "type": "object",
        "required": [
          "object",
          "receipt_id",
          "environment",
          "checks_version",
          "checked_at",
          "risk",
          "signals",
          "checks",
          "warnings"
        ],
        "properties": {
          "object": {
            "const": "receipt_checks"
          },
          "receipt_id": {
            "type": "string",
            "pattern": "^rcp_[0-9a-f]{32}$"
          },
          "environment": {
            "enum": [
              "live",
              "test"
            ]
          },
          "checks_version": {
            "type": "string",
            "description": "Version of the rules (a date)."
          },
          "checked_at": {
            "type": "string",
            "format": "date-time"
          },
          "risk": {
            "enum": [
              "none",
              "low",
              "medium",
              "high"
            ],
            "description": "The highest severity among the signals."
          },
          "signals": {
            "type": "array",
            "description": "Highest severity first.",
            "items": {
              "type": "object",
              "required": [
                "code",
                "severity",
                "message"
              ],
              "properties": {
                "code": {
                  "type": "string",
                  "description": "Stable code (codes are added, never changed): duplicate, payment_amount_mismatch, payment_date_mismatch, payment_merchant_mismatch, payment_currency_differs, ai_generated, ai_edited, edited_with_software, photo_taken_before_receipt_date, totals_do_not_add_up, line_arithmetic, tax_mismatch, screenshot, not_a_camera_photo."
                },
                "severity": {
                  "enum": [
                    "low",
                    "medium",
                    "high"
                  ]
                },
                "message": {
                  "type": "string",
                  "description": "For people; may be reworded."
                }
              }
            }
          },
          "checks": {
            "type": "object",
            "required": [
              "arithmetic",
              "duplicates",
              "payment",
              "file"
            ],
            "properties": {
              "arithmetic": {
                "type": "object",
                "required": [
                  "status",
                  "issues"
                ],
                "properties": {
                  "status": {
                    "enum": [
                      "passed",
                      "failed",
                      "not_applicable"
                    ]
                  },
                  "issues": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "code"
                      ],
                      "properties": {
                        "code": {
                          "enum": [
                            "line_total_mismatch",
                            "weight_price_mismatch",
                            "totals_do_not_add_up",
                            "tax_mismatch"
                          ]
                        },
                        "line": {
                          "type": "integer"
                        },
                        "expected": {
                          "type": "number"
                        },
                        "found": {
                          "type": "number"
                        },
                        "delta": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              },
              "duplicates": {
                "type": "object",
                "required": [
                  "status",
                  "matches",
                  "total_matches"
                ],
                "properties": {
                  "status": {
                    "enum": [
                      "passed",
                      "failed",
                      "not_applicable"
                    ]
                  },
                  "total_matches": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "matches": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "type": "object",
                      "required": [
                        "receipt_id",
                        "created_at",
                        "match"
                      ],
                      "properties": {
                        "receipt_id": {
                          "type": "string",
                          "pattern": "^rcp_[0-9a-f]{32}$"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "match": {
                          "enum": [
                            "same_file",
                            "same_lines",
                            "same_merchant_date_total"
                          ]
                        }
                      }
                    }
                  }
                }
              },
              "payment": {
                "type": "object",
                "required": [
                  "status",
                  "amount",
                  "date",
                  "merchant"
                ],
                "properties": {
                  "status": {
                    "enum": [
                      "passed",
                      "failed",
                      "incomplete",
                      "not_requested"
                    ]
                  },
                  "amount": {
                    "type": "object",
                    "required": [
                      "result",
                      "receipt",
                      "expected",
                      "difference"
                    ],
                    "properties": {
                      "result": {
                        "enum": [
                          "match",
                          "mismatch",
                          "not_comparable",
                          "unknown",
                          "not_requested"
                        ]
                      },
                      "receipt": {
                        "type": [
                          "number",
                          "null"
                        ]
                      },
                      "expected": {
                        "type": [
                          "number",
                          "null"
                        ]
                      },
                      "difference": {
                        "type": [
                          "number",
                          "null"
                        ]
                      }
                    }
                  },
                  "date": {
                    "type": "object",
                    "required": [
                      "result",
                      "receipt",
                      "expected",
                      "days_apart"
                    ],
                    "properties": {
                      "result": {
                        "enum": [
                          "match",
                          "mismatch",
                          "unknown",
                          "not_requested"
                        ]
                      },
                      "receipt": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "expected": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "days_apart": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      }
                    }
                  },
                  "merchant": {
                    "type": "object",
                    "required": [
                      "result",
                      "receipt",
                      "expected",
                      "similarity"
                    ],
                    "properties": {
                      "result": {
                        "enum": [
                          "match",
                          "mismatch",
                          "unknown",
                          "not_requested"
                        ]
                      },
                      "receipt": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "expected": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "similarity": {
                        "type": [
                          "number",
                          "null"
                        ]
                      }
                    }
                  }
                }
              },
              "file": {
                "type": "object",
                "required": [
                  "status",
                  "reason",
                  "format",
                  "camera",
                  "software",
                  "creator_tool",
                  "taken_at",
                  "content_credentials",
                  "ai_declared"
                ],
                "properties": {
                  "status": {
                    "enum": [
                      "passed",
                      "flagged",
                      "no_metadata",
                      "not_available"
                    ]
                  },
                  "reason": {
                    "enum": [
                      "test_mode",
                      "deleted_after_7_days",
                      "unavailable",
                      null
                    ]
                  },
                  "format": {
                    "enum": [
                      "jpeg",
                      "png",
                      "webp",
                      null
                    ]
                  },
                  "camera": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "software": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "creator_tool": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "taken_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Camera time, no time zone: YYYY-MM-DDTHH:MM:SS."
                  },
                  "content_credentials": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "The file carries C2PA content credentials (not a signal by itself)."
                  },
                  "ai_declared": {
                    "enum": [
                      "generated",
                      "edited",
                      null
                    ]
                  }
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "`test_mode` for test receipts."
          }
        }
      },
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. Use `code` in your code: it never changes for a given error.",
        "required": [
          "type",
          "title",
          "status",
          "code",
          "detail",
          "request_id"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "code": {
            "enum": [
              "invalid_request",
              "unauthorized",
              "insufficient_credits",
              "forbidden",
              "not_found",
              "method_not_allowed",
              "idempotency_conflict",
              "image_too_large",
              "unsupported_media_type",
              "internal_error",
              "service_unavailable",
              "receipt_not_completed"
            ]
          },
          "detail": {
            "type": "string",
            "description": "A human-readable explanation. The wording may change: do not parse it."
          },
          "request_id": {
            "type": "string"
          }
        }
      }
    }
  }
}
