{
  "openapi": "3.1.0",
  "info": {
    "title": "Invoice Service",
    "version": "2.1.0",
    "description": "A worked example, rendered by docspack from the OpenAPI document served at `/api/example.json`.\n\nIt describes a small billing API: invoices are drafted, sent, paid and voided, and every request carries a bearer token. None of it is real — `api.example.com` resolves to nothing — so this is a document to read and to generate from, not an API to call.\n\nThe same document is served as [LAPIS](https://github.com/cr0hn/LAPIS) at `/api/example.lapis`, which is what an agent would read."
  },
  "servers": [
    {
      "url": "https://api.example.com/v2",
      "description": "Production"
    },
    {
      "url": "https://sandbox.example.com/v2",
      "description": "Sandbox, where test tokens work"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Invoices",
      "description": "Drafting, sending and settling invoices."
    },
    {
      "name": "Customers",
      "description": "The party an invoice is addressed to."
    },
    {
      "name": "Service",
      "description": "Liveness, for a load balancer rather than for a person."
    }
  ],
  "paths": {
    "/invoices": {
      "get": {
        "operationId": "listInvoices",
        "summary": "Lists invoices, newest first.",
        "description": "Paginated with an opaque cursor. Pass the `next_cursor` from the previous page; a response with `has_more: false` is the last one.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "description": "Return only invoices in this state.",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "open",
                "paid",
                "void"
              ],
              "example": "open"
            }
          },
          {
            "name": "customer_id",
            "in": "query",
            "description": "Return only invoices addressed to this customer.",
            "schema": {
              "type": "string",
              "example": "cus_8ZQd41"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "How many to return. Between 1 and 100.",
            "schema": {
              "type": "integer",
              "default": 20,
              "example": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The next_cursor of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of invoices.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceList"
                }
              }
            }
          },
          "401": {
            "description": "The token is missing, expired or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createInvoice",
        "summary": "Creates a draft invoice.",
        "description": "The invoice is created in `draft` and sends nothing. `POST /invoices/{invoice_id}/send` is what puts it in front of the customer.\n\nTotals are computed from the line items; sending one is an error rather than an override.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Repeat a key to retry safely: the first invoice created under it is returned again rather than a second one being drafted.",
            "schema": {
              "type": "string",
              "example": "b0a1e6c2-7f4d-4a95-9b3a-2f1c6d8e5a44"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceCreate"
              },
              "example": {
                "customer_id": "cus_8ZQd41",
                "due_date": "2026-10-01",
                "lines": [
                  {
                    "description": "Seat licence, October",
                    "quantity": 12,
                    "unit_price": {
                      "amount": 2900,
                      "currency": "EUR"
                    }
                  }
                ],
                "notes": "Net 30."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "422": {
            "description": "The line items did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/invoices/{invoice_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/InvoiceId"
        }
      ],
      "get": {
        "operationId": "getInvoice",
        "summary": "Retrieves one invoice.",
        "tags": [
          "Invoices"
        ],
        "responses": {
          "200": {
            "description": "The invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateInvoice",
        "summary": "Changes a draft invoice.",
        "description": "Only a `draft` invoice can be changed. Once it has been sent, the way to correct it is a credit note.",
        "tags": [
          "Invoices"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceUpdate"
              },
              "example": {
                "due_date": "2026-10-15",
                "notes": "Net 45, agreed by email."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The invoice is no longer a draft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "voidInvoice",
        "summary": "Voids an invoice.",
        "description": "A voided invoice stays readable and stops being owed. Nothing is deleted: an invoice that was sent is a record.",
        "tags": [
          "Invoices"
        ],
        "responses": {
          "204": {
            "description": "Voided. No body."
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The invoice is already paid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/invoices/{invoice_id}/send": {
      "parameters": [
        {
          "$ref": "#/components/parameters/InvoiceId"
        }
      ],
      "post": {
        "operationId": "sendInvoice",
        "summary": "Sends a draft invoice to the customer.",
        "description": "Moves the invoice from `draft` to `open` and emails it. With no `to`, it goes to the customer's own address.",
        "tags": [
          "Invoices"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendOptions"
              },
              "example": {
                "cc": [
                  "ap@northwind.example"
                ],
                "message": "October seats, as discussed."
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued for delivery. The invoice is now open.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The invoice was not a draft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/invoices/{invoice_id}/pdf": {
      "parameters": [
        {
          "$ref": "#/components/parameters/InvoiceId"
        }
      ],
      "get": {
        "operationId": "downloadInvoicePdf",
        "summary": "Downloads the rendered invoice.",
        "description": "Deprecated. Every invoice now carries a `pdf_url` that is valid for an hour; fetch that instead.",
        "tags": [
          "Invoices"
        ],
        "deprecated": true,
        "responses": {
          "200": {
            "description": "The PDF.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/customers/{customer_id}": {
      "get": {
        "operationId": "getCustomer",
        "summary": "Retrieves a customer.",
        "tags": [
          "Customers"
        ],
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "cus_8ZQd41"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The customer, with the address invoices are billed to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/status": {
      "get": {
        "operationId": "getStatus",
        "summary": "Liveness.",
        "description": "The one route that takes no credential, so a load balancer can call it.",
        "tags": [
          "Service"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "version"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok",
                        "degraded"
                      ],
                      "example": "ok"
                    },
                    "version": {
                      "type": "string",
                      "example": "2.1.0"
                    },
                    "region": {
                      "type": "string",
                      "example": "eu-central"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API key",
        "description": "An API key from the dashboard. It travels as an Authorization header: Bearer sk_live_… in production, sk_test_… in the sandbox."
      }
    },
    "parameters": {
      "InvoiceId": {
        "name": "invoice_id",
        "in": "path",
        "required": true,
        "description": "The invoice's id, as returned when it was created.",
        "schema": {
          "type": "string",
          "example": "inv_3PkQ2m"
        }
      }
    },
    "responses": {
      "NotFound": {
        "description": "No such record, or the token cannot see it.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Money": {
        "type": "object",
        "description": "An amount in the currency's minor unit: 2900 is €29.00.",
        "required": [
          "amount",
          "currency"
        ],
        "properties": {
          "amount": {
            "type": "integer",
            "example": 2900
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR",
              "USD",
              "GBP"
            ],
            "example": "EUR"
          }
        }
      },
      "LineItem": {
        "type": "object",
        "required": [
          "description",
          "quantity",
          "unit_price"
        ],
        "properties": {
          "description": {
            "type": "string",
            "example": "Seat licence, October"
          },
          "quantity": {
            "type": "integer",
            "example": 12
          },
          "unit_price": {
            "$ref": "#/components/schemas/Money"
          },
          "tax_rate": {
            "type": "number",
            "description": "Percentage, as a number: 19 is 19%.",
            "example": 19
          }
        }
      },
      "Invoice": {
        "type": "object",
        "required": [
          "id",
          "customer_id",
          "status",
          "lines",
          "total",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "inv_3PkQ2m"
          },
          "customer_id": {
            "type": "string",
            "example": "cus_8ZQd41"
          },
          "status": {
            "type": "string",
            "description": "Where the invoice is in its life: drafted, sent, settled or cancelled.",
            "enum": [
              "draft",
              "open",
              "paid",
              "void"
            ],
            "example": "open"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LineItem"
            }
          },
          "total": {
            "$ref": "#/components/schemas/Money"
          },
          "due_date": {
            "type": "string",
            "format": "date",
            "example": "2026-10-01"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-09-01T09:12:44Z"
          },
          "pdf_url": {
            "type": "string",
            "format": "uri",
            "description": "A link to the rendered invoice, valid for an hour.",
            "nullable": true
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "example": "Net 30."
          },
          "metadata": {
            "type": "object",
            "description": "Your own keys, returned unchanged.",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "InvoiceCreate": {
        "type": "object",
        "required": [
          "customer_id",
          "lines"
        ],
        "properties": {
          "customer_id": {
            "type": "string",
            "example": "cus_8ZQd41"
          },
          "lines": {
            "type": "array",
            "description": "At least one. The total is the sum of these.",
            "items": {
              "$ref": "#/components/schemas/LineItem"
            }
          },
          "due_date": {
            "type": "string",
            "format": "date",
            "description": "Defaults to 30 days out.",
            "example": "2026-10-01"
          },
          "notes": {
            "type": "string",
            "example": "Net 30."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "InvoiceUpdate": {
        "type": "object",
        "description": "Every field optional: what is sent is changed, what is omitted is left alone.",
        "properties": {
          "due_date": {
            "type": "string",
            "format": "date",
            "example": "2026-10-15"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LineItem"
            }
          },
          "notes": {
            "type": "string",
            "nullable": true
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "InvoiceList": {
        "type": "object",
        "required": [
          "data",
          "has_more"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Invoice"
            }
          },
          "has_more": {
            "type": "boolean",
            "example": false
          },
          "next_cursor": {
            "type": "string",
            "description": "Pass as cursor for the next page. Null on the last one.",
            "nullable": true
          }
        }
      },
      "Address": {
        "type": "object",
        "required": [
          "line1",
          "city",
          "postal_code",
          "country"
        ],
        "properties": {
          "line1": {
            "type": "string",
            "example": "Kastanienallee 12"
          },
          "line2": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "example": "Berlin"
          },
          "postal_code": {
            "type": "string",
            "example": "10435"
          },
          "country": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2.",
            "example": "DE"
          }
        }
      },
      "Customer": {
        "type": "object",
        "required": [
          "id",
          "name",
          "email",
          "address"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "cus_8ZQd41"
          },
          "name": {
            "type": "string",
            "example": "Northwind GmbH"
          },
          "email": {
            "type": "string",
            "format": "email",
            "example": "ap@northwind.example"
          },
          "address": {
            "$ref": "#/components/schemas/Address"
          },
          "tax_id": {
            "type": "string",
            "nullable": true,
            "example": "DE123456789"
          }
        }
      },
      "SendOptions": {
        "type": "object",
        "properties": {
          "to": {
            "type": "array",
            "description": "Defaults to the customer's own address.",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "cc": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "message": {
            "type": "string",
            "description": "A line above the invoice in the email.",
            "example": "October seats, as discussed."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "type",
              "message"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "authentication_failed",
                  "not_found",
                  "conflict",
                  "rate_limited"
                ],
                "example": "invalid_request"
              },
              "message": {
                "type": "string",
                "description": "One sentence, written for a developer reading a log.",
                "example": "lines must contain at least one item."
              },
              "param": {
                "type": "string",
                "description": "The field at fault, when one field is.",
                "nullable": true,
                "example": "lines"
              }
            }
          }
        }
      }
    }
  }
}
