Causa Prima Invoices API

The Invoices API from Causa Prima — 3 operation(s) for invoices.

OpenAPI Specification

causa-prima-invoices-api-openapi.yml Raw ↑
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.