openapi: 3.1.0 info: title: Scribo Invoice Invoices API version: 1.0.0 description: Compliant e-invoice generation. **Phase 1 supports Germany (XRechnung / ZUGFeRD) and the United States (plain PDF) only** — any other sender jurisdiction is rejected with `unsupported_jurisdiction`. The `/api/v1/*` namespace served from `scribo.causaprima.ai` is the public surface — anonymous and rate-limited. The internal `/internal/scribo/*` namespace is used by the Causa Prima web app and MCP server and is not part of this contract. contact: name: Causa Prima url: https://causaprima.ai servers: - url: https://scribo.causaprima.ai description: Production - url: http://localhost:3200 description: Local development (scribo Next.js proxy) tags: - name: Invoices paths: /api/v1/invoices: post: summary: Generate a compliant e-invoice description: Anonymous endpoint. Rate-limited per source IP. Returns 201 with a download URL. parameters: - schema: type: string minLength: 1 maxLength: 255 description: Client-supplied key. Replaying the same key with identical inputs returns the cached invoice; replaying with different inputs returns 422 idempotency_key_mismatch. required: false name: Idempotency-Key in: header - schema: type: string minLength: 1 description: Email-ownership token from POST /api/v1/scribo/email-verifications/{id}/redeem. Required for the first invoice per sender email — without it (or a scribo session cookie) an unverified sender receives 401 email_verification_required. required: false name: X-Email-Verification-Token in: header requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateInvoiceInput' responses: '201': description: Invoice generated and persisted content: application/json: schema: $ref: '#/components/schemas/Invoice' '400': description: Invalid input or downstream validator rejection content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '401': description: Sender email not verified yet (email_verification_required) — run the email-verification flow and retry with X-Email-Verification-Token content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '403': description: Verified email does not match sender.contact_email (verification_email_mismatch), or tenant is temporarily soft-blocked after repeated hard-limit hits (tenant_soft_blocked) content: application/json: schema: anyOf: - $ref: '#/components/schemas/ErrorEnvelope' - $ref: '#/components/schemas/TenantSoftBlockError' '422': description: Idempotency-Key mismatch content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '429': description: Rate limit exceeded headers: Retry-After: schema: type: integer description: Seconds until the limit resets. content: application/json: schema: $ref: '#/components/schemas/RateLimitError' tags: - Invoices /api/v1/invoices/{id}: get: summary: Fetch invoice metadata description: Returns the Invoice for a previously-generated invoice. Read path is wired alongside the scribo session cookie; cross-tenant probes return 404. parameters: - schema: type: string format: uuid description: Invoice UUID required: true name: id in: path responses: '200': description: Invoice metadata content: application/json: schema: $ref: '#/components/schemas/Invoice' '404': description: Invoice not found or not visible to this caller content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' tags: - Invoices /api/v1/invoices/{id}/download: get: summary: Download the invoice file description: Streams the generated PDF (or hybrid PDF/A-3 with embedded XML) bytes. Future revisions may 302 to a signed object-storage URL. parameters: - schema: type: string format: uuid required: true name: id in: path responses: '200': description: Invoice bytes content: application/pdf: schema: type: string format: binary '404': description: Artifact not found content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' tags: - Invoices components: schemas: Invoice: type: object properties: invoice_id: type: string format: uuid document_id: type: string format: uuid format: type: string enum: - zugferd_comfort - zugferd_basic - xrechnung_cii - xrechnung_ubl - plain_pdf download_url: type: string format: uri download_url_expires_at: type: string format: date-time preview_url: type: string format: uri preview_url_expires_at: type: string format: date-time submission: type: object properties: status: type: string enum: - not_provided message: type: string manual_upload_hints: type: array items: type: object properties: label: type: string url: type: string format: uri leitweg_prefix: type: string required: - label - url default: [] planned: type: array items: type: string default: [] required: - status - message validator_summary: type: object properties: valid: type: boolean validator: type: string errors: type: array items: type: object properties: path: type: string rule: type: string message: type: string required: - message default: [] required: - valid - validator magic_link_sent: type: boolean recipient_email_sent: type: boolean required: - invoice_id - document_id - format - download_url - download_url_expires_at - validator_summary - magic_link_sent RateLimitError: type: object properties: error: type: object properties: code: type: string enum: - rate_limited - tenant_invoice_quota_exceeded example: tenant_invoice_quota_exceeded message: type: string retry_after_seconds: type: integer minimum: 0 reset_at: type: string format: date-time limit_code: type: string required: - code - message - retry_after_seconds - reset_at - limit_code required: - error CreateInvoiceInput: type: object properties: sender: type: object properties: legal_name: type: string minLength: 1 maxLength: 200 country_code: type: string pattern: ^[A-Z]{2}$ address_line1: type: string minLength: 1 maxLength: 200 address_line2: type: string maxLength: 200 postcode: type: string minLength: 1 maxLength: 20 city: type: string minLength: 1 maxLength: 100 tax_id: type: string minLength: 1 maxLength: 40 contact_email: type: string format: email contact_phone: type: string minLength: 3 maxLength: 40 contact_name: type: string minLength: 1 maxLength: 200 tax_registration_id: type: string minLength: 1 maxLength: 50 required: - legal_name - country_code - address_line1 - postcode - city - contact_email recipient: type: object properties: legal_name: type: string minLength: 1 maxLength: 200 country_code: type: string pattern: ^[A-Z]{2}$ address_line1: type: string minLength: 1 maxLength: 200 address_line2: type: string maxLength: 200 postcode: type: string minLength: 1 maxLength: 20 city: type: string minLength: 1 maxLength: 100 tax_id: type: string minLength: 1 maxLength: 40 contact_email: type: string format: email leitweg_id: type: string minLength: 1 maxLength: 50 required: - legal_name - country_code - address_line1 - postcode - city - contact_email line_items: type: array items: type: object properties: description: type: string minLength: 1 maxLength: 500 quantity: type: string pattern: ^(?:[1-9][0-9]*|0\.[0-9]*[1-9][0-9]*|[1-9][0-9]*\.[0-9]+)$ unit_code: type: string minLength: 1 maxLength: 10 default: EA unit_price: type: string pattern: ^(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$ tax_rate: type: string pattern: ^(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$ tax_category_code: type: string enum: - S - Z - E - AE - K - G - O discount: oneOf: - type: object properties: type: type: string enum: - amount value: type: string pattern: ^(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$ reason: type: string minLength: 1 maxLength: 200 required: - type - value - reason - type: object properties: type: type: string enum: - percent value: type: string pattern: ^(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$ reason: type: string minLength: 1 maxLength: 200 required: - type - value - reason tax_exemption_code: type: string pattern: ^VATEX-(EU|[A-Z]{2})-[A-Z0-9-]+$ tax_exemption_reason: type: string minLength: 1 maxLength: 1000 required: - description - quantity - unit_price - tax_rate - tax_category_code minItems: 1 maxItems: 500 currency: type: string pattern: ^[A-Z]{3}$ jurisdiction: type: string pattern: ^[A-Z]{2}$ format_override: type: string enum: - zugferd_comfort - zugferd_basic - xrechnung_cii - xrechnung_ubl - plain_pdf notes: type: string maxLength: 1000 invoice_number: type: string pattern: ^[\p{L}\p{N} \-/.#_]{1,64}$ description: 'Optional invoice number printed on the document (BT-1). Omit the field entirely to have the server assign one — do NOT send a placeholder like "auto", "TBD", or "N/A" (these are ignored and a real code is assigned). When present: 1-64 chars of letters, digits, space, or - / . # _.' issue_date: type: string pattern: ^\d{4}-\d{2}-\d{2}$ description: Optional ISO date (BT-2) — the invoice's issue date. Defaults to today when omitted. due_date: type: string pattern: ^\d{4}-\d{2}-\d{2}$ payment_terms: type: string minLength: 1 maxLength: 200 payment_means: type: object properties: type: type: string enum: - credit_transfer iban: type: string pattern: ^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$ bic: type: string pattern: ^[A-Z][A-Z0-9]{7}([A-Z0-9]{3})?$ account_number: type: string pattern: ^\d{4,17}$ routing_number: type: string pattern: ^\d{9}$ account_name: type: string minLength: 1 maxLength: 200 bank: type: string minLength: 1 maxLength: 300 required: - type description: 'BG-16 payment instructions for a credit transfer. Provide EXACTLY ONE of: a SEPA `iban`, OR US domestic details (`account_number` + 9-digit ABA `routing_number`). An optional `bic` (SWIFT) may accompany either form — US accounts carry a SWIFT/BIC for inbound international wires. An optional free-text `bank` (beneficiary bank name + address) may also be included; it''s shown on the invoice but not validated structurally. Supplying both account forms, neither, or only half of the US pair is rejected. XRechnung (German B2G) requires the `iban` form (BR-DE-1).' delivery_date: type: string pattern: ^\d{4}-\d{2}-\d{2}$ delivery_period: type: object properties: start: type: string pattern: ^\d{4}-\d{2}-\d{2}$ end: type: string pattern: ^\d{4}-\d{2}-\d{2}$ required: - start - end required: - sender - recipient - line_items - currency ErrorEnvelope: type: object properties: error: type: object properties: code: type: string example: invalid_input message: type: string details: {} required: - code - message required: - error TenantSoftBlockError: type: object properties: error: type: object properties: code: type: string enum: - tenant_soft_blocked message: type: string retry_after_seconds: type: integer minimum: 0 reset_at: type: string format: date-time limit_code: type: string enum: - tenant_hard_limit_hits_per_hour details: type: object properties: reason_code: type: string enum: - tenant_hard_limit_hits_per_hour auto_lift_at: type: string format: date-time required: - reason_code - auto_lift_at required: - code - message - retry_after_seconds - reset_at - limit_code - details required: - error securitySchemes: bearerAuth: type: http scheme: bearer description: Optional API key for elevated quotas. Unauthenticated requests are accepted and rate-limited per IP.