{
  "openapi": "3.0.3",
  "info": {
    "title": "Finero Public API",
    "version": "1.0.0",
    "description": "Tenant-scoped public API for Finero — AR / cash-flow automation. Authenticate every request with `Authorization: Bearer <API key>` (keys are created in Settings → API and are tenant-owned). All data belongs to the key's tenant; no request input can address another tenant. Two permissions exist: **admin** keys can call every operation; **readonly** keys only operations without side effects (`x-permission: readonly`). Lists are cursor-paginated (limit 1–100, follow pagination.next_cursor). Request bodies are capped at 64 KiB. Rate limits: 120/min per key, 600/min per tenant (429 + Retry-After). Decimal money amounts are exact strings (never floats); *_minor amounts are integers in the currency's minor unit. Timestamps are ISO 8601 UTC."
  },
  "servers": [
    {
      "url": "https://api.getfinero.com/functions/v1/api"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "API context"
    },
    {
      "name": "Invoices"
    },
    {
      "name": "Payment links"
    },
    {
      "name": "Payments"
    },
    {
      "name": "ERP sync"
    },
    {
      "name": "Workflows"
    }
  ],
  "paths": {
    "/v1/erp-connections": {
      "get": {
        "operationId": "listErpConnections",
        "summary": "List ERP connections",
        "description": "Lists the tenant's ERP connections (safe status metadata only — never credentials, hosts, or configuration). Read-only visibility: connecting, configuring, and syncing are managed inside the Finero app.\n\nAvailable to **admin and readonly** API keys (no side effects).",
        "tags": [
          "ERP sync"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-permission": "readonly",
        "x-side-effects": false,
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100. Default 25.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque pagination cursor from a previous response's pagination.next_cursor.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 400
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort direction by creation time. Default desc (newest first).",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "description": "ERP connections.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "ERP connection id — use as connection_id when starting a sync run.",
                            "format": "uuid"
                          },
                          "provider": {
                            "type": "string",
                            "description": "ERP system this connection syncs from.",
                            "enum": [
                              "oracle_fusion"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "description": "Connection health.",
                            "enum": [
                              "connected",
                              "requires_reauthentication",
                              "temporarily_unavailable",
                              "disconnected"
                            ]
                          },
                          "last_sync_at": {
                            "type": "string",
                            "description": "Last successful sync completion (ISO 8601 UTC).",
                            "nullable": true,
                            "format": "date-time"
                          },
                          "auto_sync_enabled": {
                            "type": "boolean",
                            "description": "Whether scheduled auto-sync is on."
                          },
                          "auto_sync_interval_minutes": {
                            "type": "integer",
                            "description": "Scheduled auto-sync cadence in minutes."
                          },
                          "created_at": {
                            "type": "string",
                            "description": "Creation time (ISO 8601 UTC).",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "provider",
                          "status",
                          "last_sync_at",
                          "auto_sync_enabled",
                          "auto_sync_interval_minutes",
                          "created_at"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "next_cursor": {
                          "type": "string",
                          "description": "Opaque cursor for the next page — pass as ?cursor=. Null when there are no further results.",
                          "nullable": true
                        },
                        "has_more": {
                          "type": "boolean",
                          "description": "Whether another page exists."
                        },
                        "limit": {
                          "type": "integer",
                          "description": "The page size that was applied."
                        }
                      },
                      "required": [
                        "next_cursor",
                        "has_more",
                        "limit"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "data": [
                    {
                      "id": "1a2b3c4d-5e6f-4a80-91b2-c3d4e5f60718",
                      "provider": "oracle_fusion",
                      "status": "connected",
                      "last_sync_at": "2026-07-14T05:00:04+00:00",
                      "auto_sync_enabled": true,
                      "auto_sync_interval_minutes": 60,
                      "created_at": "2026-06-20T11:00:00+00:00"
                    }
                  ],
                  "pagination": {
                    "next_cursor": null,
                    "has_more": false,
                    "limit": 25
                  }
                }
              }
            }
          },
          "400": {
            "description": "`validation_failed` — The request failed schema validation. See error.details for field messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_credentials` — The credential is malformed or does not match an active key. The response never reveals whether a key identifier exists. `missing_credentials` — No Authorization: Bearer header was sent. `revoked_credentials` — The presented key was revoked. Create a new key in Settings → API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`feature_unavailable` — The tenant's tier does not currently include API access. Feature rollout flags cannot grant this entitlement. `permission_denied` — The key's permission does not allow this operation (readonly keys cannot call admin-only operations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No such resource in YOUR tenant. Ids belonging to another tenant are indistinguishable from missing ones.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limit_exceeded` — Too many requests. Honor the Retry-After header (seconds) before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Unexpected server error. Safe to retry with the same Idempotency-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/invoices": {
      "get": {
        "operationId": "listInvoices",
        "summary": "List invoices",
        "description": "Lists the tenant's synced invoices, newest first. Cursor-paginated: pass ?limit= (1–100) and follow pagination.next_cursor until has_more is false. Filters are allowlisted; combine freely.\n\nAvailable to **admin and readonly** API keys (no side effects).",
        "tags": [
          "Invoices"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-permission": "readonly",
        "x-side-effects": false,
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100. Default 25.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque pagination cursor from a previous response's pagination.next_cursor.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 400
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort direction by creation time. Default desc (newest first).",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "finalization_state",
            "in": "query",
            "required": false,
            "description": "Filter by the ERP document-lifecycle state (final/draft/cancelled/unknown).",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "final",
                "cancelled",
                "unknown"
              ]
            }
          },
          {
            "name": "collection_ready",
            "in": "query",
            "required": false,
            "description": "Filter by collection readiness.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "description": "Filter by ISO 4217 currency code.",
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 3
            }
          },
          {
            "name": "invoice_number",
            "in": "query",
            "required": false,
            "description": "Exact-match filter on the ERP invoice number.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            }
          },
          {
            "name": "connection_id",
            "in": "query",
            "required": false,
            "description": "Filter by ERP connection.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "description": "Only invoices updated at or after this ISO 8601 timestamp.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "description": "Invoices, newest first.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Invoice id (stable Finero identifier).",
                            "format": "uuid"
                          },
                          "invoice_number": {
                            "type": "string",
                            "description": "Human-readable local or ERP invoice number.",
                            "nullable": true
                          },
                          "finalization_state": {
                            "type": "string",
                            "description": "ERP document-lifecycle state: final = finalized in the ERP, draft = not yet finalized, cancelled = voided in the ERP, unknown = unmappable source value. Only final invoices are collectible. Not a payment state.",
                            "enum": [
                              "draft",
                              "final",
                              "cancelled",
                              "unknown"
                            ]
                          },
                          "currency": {
                            "type": "string",
                            "description": "Invoice currency. ISO 4217 alphabetic code, e.g. \"USD\".",
                            "nullable": true,
                            "minLength": 3,
                            "maxLength": 3
                          },
                          "total_amount": {
                            "type": "string",
                            "description": "Invoice total. Decimal amount as a string (exact, 4 dp) — never parse as float for arithmetic.",
                            "nullable": true,
                            "pattern": "^-?\\d+(\\.\\d+)?$"
                          },
                          "open_balance": {
                            "type": "string",
                            "description": "Outstanding balance across the invoice. Decimal amount as a string (exact, 4 dp) — never parse as float for arithmetic.",
                            "nullable": true,
                            "pattern": "^-?\\d+(\\.\\d+)?$"
                          },
                          "invoice_date": {
                            "type": "string",
                            "description": "Invoice date (YYYY-MM-DD).",
                            "nullable": true,
                            "format": "date"
                          },
                          "due_date": {
                            "type": "string",
                            "description": "Invoice due date (YYYY-MM-DD).",
                            "nullable": true,
                            "format": "date"
                          },
                          "collection_ready": {
                            "type": "boolean",
                            "description": "Whether the invoice is eligible for payment-link collection (authoritative readiness flag)."
                          },
                          "customer": {
                            "type": "object",
                            "description": "Bill-to customer details embedded on the invoice (Finero has no standalone customer entity).",
                            "properties": {
                              "number": {
                                "type": "string",
                                "description": "Bill-to customer number from the ERP.",
                                "nullable": true
                              },
                              "name": {
                                "type": "string",
                                "description": "Bill-to customer name.",
                                "nullable": true
                              },
                              "email": {
                                "type": "string",
                                "description": "Billing email the payment-link email is sent to.",
                                "nullable": true
                              }
                            },
                            "required": [
                              "number",
                              "name",
                              "email"
                            ],
                            "additionalProperties": false
                          },
                          "origin": {
                            "type": "string",
                            "description": "Provenance / system of record: local = the invoice exists only in Finero; erp = an ERP connection owns (or will own) the record.",
                            "enum": [
                              "local",
                              "erp"
                            ]
                          },
                          "connection_id": {
                            "type": "string",
                            "description": "The exact ERP connection associated with this invoice.",
                            "nullable": true,
                            "format": "uuid"
                          },
                          "created_at": {
                            "type": "string",
                            "description": "Creation time (ISO 8601 UTC).",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "description": "Last update time (ISO 8601 UTC).",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "invoice_number",
                          "finalization_state",
                          "currency",
                          "total_amount",
                          "open_balance",
                          "invoice_date",
                          "due_date",
                          "collection_ready",
                          "customer",
                          "origin",
                          "connection_id",
                          "created_at",
                          "updated_at"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "next_cursor": {
                          "type": "string",
                          "description": "Opaque cursor for the next page — pass as ?cursor=. Null when there are no further results.",
                          "nullable": true
                        },
                        "has_more": {
                          "type": "boolean",
                          "description": "Whether another page exists."
                        },
                        "limit": {
                          "type": "integer",
                          "description": "The page size that was applied."
                        }
                      },
                      "required": [
                        "next_cursor",
                        "has_more",
                        "limit"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "data": [
                    {
                      "id": "8f14e45f-ceea-4a5b-9d2c-167ce7de1a10",
                      "invoice_number": "INV-10421",
                      "finalization_state": "final",
                      "currency": "USD",
                      "total_amount": "1250.0000",
                      "open_balance": "1250.0000",
                      "invoice_date": "2026-07-01",
                      "due_date": "2026-08-01",
                      "collection_ready": true,
                      "customer": {
                        "number": "CUST-2201",
                        "name": "Acme Industries Ltd",
                        "email": "ap@acme-industries.example"
                      },
                      "origin": "erp",
                      "connection_id": "1a2b3c4d-5e6f-4a80-91b2-c3d4e5f60718",
                      "created_at": "2026-07-01T09:15:00+00:00",
                      "updated_at": "2026-07-10T06:30:00+00:00"
                    }
                  ],
                  "pagination": {
                    "next_cursor": null,
                    "has_more": false,
                    "limit": 25
                  }
                }
              }
            }
          },
          "400": {
            "description": "`validation_failed` — The request failed schema validation. See error.details for field messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_credentials` — The credential is malformed or does not match an active key. The response never reveals whether a key identifier exists. `missing_credentials` — No Authorization: Bearer header was sent. `revoked_credentials` — The presented key was revoked. Create a new key in Settings → API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`feature_unavailable` — The tenant's tier does not currently include API access. Feature rollout flags cannot grant this entitlement. `permission_denied` — The key's permission does not allow this operation (readonly keys cannot call admin-only operations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No such resource in YOUR tenant. Ids belonging to another tenant are indistinguishable from missing ones.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limit_exceeded` — Too many requests. Honor the Retry-After header (seconds) before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Unexpected server error. Safe to retry with the same Idempotency-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/invoices/{invoiceId}": {
      "get": {
        "operationId": "getInvoice",
        "summary": "Get an invoice with its installments",
        "description": "Returns one invoice including its installments (ordered by sequence). Use an installment's id to create a payment link. Returns not_found for ids outside your tenant.\n\nAvailable to **admin and readonly** API keys (no side effects).",
        "tags": [
          "Invoices"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-permission": "readonly",
        "x-side-effects": false,
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "description": "Invoice id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Invoice id (stable Finero identifier).",
                      "format": "uuid"
                    },
                    "invoice_number": {
                      "type": "string",
                      "description": "Human-readable local or ERP invoice number.",
                      "nullable": true
                    },
                    "finalization_state": {
                      "type": "string",
                      "description": "ERP document-lifecycle state: final = finalized in the ERP, draft = not yet finalized, cancelled = voided in the ERP, unknown = unmappable source value. Only final invoices are collectible. Not a payment state.",
                      "enum": [
                        "draft",
                        "final",
                        "cancelled",
                        "unknown"
                      ]
                    },
                    "currency": {
                      "type": "string",
                      "description": "Invoice currency. ISO 4217 alphabetic code, e.g. \"USD\".",
                      "nullable": true,
                      "minLength": 3,
                      "maxLength": 3
                    },
                    "total_amount": {
                      "type": "string",
                      "description": "Invoice total. Decimal amount as a string (exact, 4 dp) — never parse as float for arithmetic.",
                      "nullable": true,
                      "pattern": "^-?\\d+(\\.\\d+)?$"
                    },
                    "open_balance": {
                      "type": "string",
                      "description": "Outstanding balance across the invoice. Decimal amount as a string (exact, 4 dp) — never parse as float for arithmetic.",
                      "nullable": true,
                      "pattern": "^-?\\d+(\\.\\d+)?$"
                    },
                    "invoice_date": {
                      "type": "string",
                      "description": "Invoice date (YYYY-MM-DD).",
                      "nullable": true,
                      "format": "date"
                    },
                    "due_date": {
                      "type": "string",
                      "description": "Invoice due date (YYYY-MM-DD).",
                      "nullable": true,
                      "format": "date"
                    },
                    "collection_ready": {
                      "type": "boolean",
                      "description": "Whether the invoice is eligible for payment-link collection (authoritative readiness flag)."
                    },
                    "customer": {
                      "type": "object",
                      "description": "Bill-to customer details embedded on the invoice (Finero has no standalone customer entity).",
                      "properties": {
                        "number": {
                          "type": "string",
                          "description": "Bill-to customer number from the ERP.",
                          "nullable": true
                        },
                        "name": {
                          "type": "string",
                          "description": "Bill-to customer name.",
                          "nullable": true
                        },
                        "email": {
                          "type": "string",
                          "description": "Billing email the payment-link email is sent to.",
                          "nullable": true
                        }
                      },
                      "required": [
                        "number",
                        "name",
                        "email"
                      ],
                      "additionalProperties": false
                    },
                    "origin": {
                      "type": "string",
                      "description": "Provenance / system of record: local = the invoice exists only in Finero; erp = an ERP connection owns (or will own) the record.",
                      "enum": [
                        "local",
                        "erp"
                      ]
                    },
                    "connection_id": {
                      "type": "string",
                      "description": "The exact ERP connection associated with this invoice.",
                      "nullable": true,
                      "format": "uuid"
                    },
                    "created_at": {
                      "type": "string",
                      "description": "Creation time (ISO 8601 UTC).",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "description": "Last update time (ISO 8601 UTC).",
                      "format": "date-time"
                    },
                    "installments": {
                      "type": "array",
                      "description": "The invoice's installments, ordered by sequence.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Installment id — use as installment_id when creating a payment link.",
                            "format": "uuid"
                          },
                          "sequence": {
                            "type": "integer",
                            "description": "1-based installment sequence within the invoice."
                          },
                          "original_amount": {
                            "type": "string",
                            "description": "Original installment amount. Decimal amount as a string (exact, 4 dp) — never parse as float for arithmetic.",
                            "pattern": "^-?\\d+(\\.\\d+)?$"
                          },
                          "outstanding_amount": {
                            "type": "string",
                            "description": "Outstanding (still payable) amount. Decimal amount as a string (exact, 4 dp) — never parse as float for arithmetic.",
                            "pattern": "^-?\\d+(\\.\\d+)?$"
                          },
                          "disputed_amount": {
                            "type": "string",
                            "description": "Amount under dispute (blocks collection when > 0). Decimal amount as a string (exact, 4 dp) — never parse as float for arithmetic.",
                            "pattern": "^-?\\d+(\\.\\d+)?$"
                          },
                          "due_date": {
                            "type": "string",
                            "description": "Installment due date (YYYY-MM-DD).",
                            "nullable": true,
                            "format": "date"
                          },
                          "source_status": {
                            "type": "string",
                            "description": "Raw installment status from the ERP."
                          },
                          "payment_state": {
                            "type": "string",
                            "description": "Whether the money is in: `paid` once the installment is settled (in the ERP, or by a Finero payment the ERP has not synced back yet); `inactive` when payment is no longer expected because the receivable ended — cancelled, voided, credited or written off — so do NOT count it as revenue; otherwise `open`. Note `inactive` is about the MONEY and is distinct from the invoice's `finalization_state: cancelled`, which is the ERP document's own lifecycle. Independent of `collectible` — an open installment can still be uncollectible, and a due date being in the past does not change this field.",
                            "enum": [
                              "paid",
                              "open",
                              "inactive"
                            ]
                          },
                          "collectible": {
                            "type": "boolean",
                            "description": "Whether Finero considers this installment collectible right now. False whenever payment_state is `paid`, and also for open installments blocked by a dispute, an exclusion, a non-final invoice, or a missing billing email."
                          },
                          "excluded_from_collections": {
                            "type": "boolean",
                            "description": "Manually excluded from collections."
                          },
                          "created_at": {
                            "type": "string",
                            "description": "Creation time (ISO 8601 UTC).",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "description": "Last update time (ISO 8601 UTC).",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "sequence",
                          "original_amount",
                          "outstanding_amount",
                          "disputed_amount",
                          "due_date",
                          "source_status",
                          "payment_state",
                          "collectible",
                          "excluded_from_collections",
                          "created_at",
                          "updated_at"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "id",
                    "invoice_number",
                    "finalization_state",
                    "currency",
                    "total_amount",
                    "open_balance",
                    "invoice_date",
                    "due_date",
                    "collection_ready",
                    "customer",
                    "origin",
                    "connection_id",
                    "created_at",
                    "updated_at",
                    "installments"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "id": "8f14e45f-ceea-4a5b-9d2c-167ce7de1a10",
                  "invoice_number": "INV-10421",
                  "finalization_state": "final",
                  "currency": "USD",
                  "total_amount": "1250.0000",
                  "open_balance": "1250.0000",
                  "invoice_date": "2026-07-01",
                  "due_date": "2026-08-01",
                  "collection_ready": true,
                  "customer": {
                    "number": "CUST-2201",
                    "name": "Acme Industries Ltd",
                    "email": "ap@acme-industries.example"
                  },
                  "origin": "erp",
                  "connection_id": "1a2b3c4d-5e6f-4a80-91b2-c3d4e5f60718",
                  "created_at": "2026-07-01T09:15:00+00:00",
                  "updated_at": "2026-07-10T06:30:00+00:00",
                  "installments": [
                    {
                      "id": "a3c9d2e1-55b4-4c8e-9f01-2b3c4d5e6f70",
                      "sequence": 1,
                      "original_amount": "1250.0000",
                      "outstanding_amount": "1250.0000",
                      "disputed_amount": "0.0000",
                      "due_date": "2026-08-01",
                      "source_status": "OPEN",
                      "payment_state": "open",
                      "collectible": true,
                      "excluded_from_collections": false,
                      "created_at": "2026-07-01T09:15:00+00:00",
                      "updated_at": "2026-07-10T06:30:00+00:00"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`validation_failed` — The request failed schema validation. See error.details for field messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_credentials` — The credential is malformed or does not match an active key. The response never reveals whether a key identifier exists. `missing_credentials` — No Authorization: Bearer header was sent. `revoked_credentials` — The presented key was revoked. Create a new key in Settings → API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`feature_unavailable` — The tenant's tier does not currently include API access. Feature rollout flags cannot grant this entitlement. `permission_denied` — The key's permission does not allow this operation (readonly keys cannot call admin-only operations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No such resource in YOUR tenant. Ids belonging to another tenant are indistinguishable from missing ones.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limit_exceeded` — Too many requests. Honor the Retry-After header (seconds) before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Unexpected server error. Safe to retry with the same Idempotency-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "operationId": "getApiContext",
        "summary": "Identify the calling API key",
        "description": "Returns the tenant and key metadata for the presented credential. Useful as a connectivity/permission check before other calls. No side effects.\n\nAvailable to **admin and readonly** API keys (no side effects).",
        "tags": [
          "API context"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-permission": "readonly",
        "x-side-effects": false,
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tenant_id": {
                      "type": "string",
                      "description": "The tenant this API key belongs to. All data is scoped to it.",
                      "format": "uuid"
                    },
                    "tenant_name": {
                      "type": "string",
                      "description": "Tenant display name."
                    },
                    "api_key": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "API key id.",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string",
                          "description": "Key name given at creation."
                        },
                        "permission": {
                          "type": "string",
                          "description": "admin can call every operation; readonly only side-effect-free ones.",
                          "enum": [
                            "admin",
                            "readonly"
                          ]
                        },
                        "created_at": {
                          "type": "string",
                          "description": "Key creation time (ISO 8601 UTC).",
                          "format": "date-time"
                        },
                        "last_used_at": {
                          "type": "string",
                          "description": "Previous use of this key (ISO 8601 UTC).",
                          "nullable": true,
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "permission",
                        "created_at",
                        "last_used_at"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "tenant_id",
                    "tenant_name",
                    "api_key"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "tenant_id": "5b0f6a7e-2f5d-4d24-9e6b-3c1a2b4d5e6f",
                  "tenant_name": "Acme Industries",
                  "api_key": {
                    "id": "4d3c2b1a-0f9e-48d7-b6c5-a4b3c2d1e0f9",
                    "name": "Zapier integration",
                    "permission": "readonly",
                    "created_at": "2026-07-01T08:00:00+00:00",
                    "last_used_at": "2026-07-14T06:59:31+00:00"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`validation_failed` — The request failed schema validation. See error.details for field messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_credentials` — The credential is malformed or does not match an active key. The response never reveals whether a key identifier exists. `missing_credentials` — No Authorization: Bearer header was sent. `revoked_credentials` — The presented key was revoked. Create a new key in Settings → API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`feature_unavailable` — The tenant's tier does not currently include API access. Feature rollout flags cannot grant this entitlement. `permission_denied` — The key's permission does not allow this operation (readonly keys cannot call admin-only operations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No such resource in YOUR tenant. Ids belonging to another tenant are indistinguishable from missing ones.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limit_exceeded` — Too many requests. Honor the Retry-After header (seconds) before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Unexpected server error. Safe to retry with the same Idempotency-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/payment-links": {
      "get": {
        "operationId": "listPaymentLinks",
        "summary": "List payment links",
        "description": "Lists the tenant's payment links, newest first. Cursor-paginated: pass ?limit= (1–100) and follow pagination.next_cursor until has_more is false. The url field is the live hosted checkout page.\n\nAvailable to **admin and readonly** API keys (no side effects).",
        "tags": [
          "Payment links"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-permission": "readonly",
        "x-side-effects": false,
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100. Default 25.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque pagination cursor from a previous response's pagination.next_cursor.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 400
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort direction by creation time. Default desc (newest first).",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by lifecycle status.",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "processing",
                "paid",
                "inactive",
                "needs_replacement",
                "action_required"
              ]
            }
          },
          {
            "name": "invoice_id",
            "in": "query",
            "required": false,
            "description": "Filter by invoice.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "installment_id",
            "in": "query",
            "required": false,
            "description": "Filter by installment.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "description": "Payment links, newest first.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Payment link id.",
                            "format": "uuid"
                          },
                          "invoice_id": {
                            "type": "string",
                            "description": "Invoice the link collects.",
                            "format": "uuid"
                          },
                          "installment_id": {
                            "type": "string",
                            "description": "Installment the link collects (one active link per installment).",
                            "format": "uuid"
                          },
                          "status": {
                            "type": "string",
                            "description": "Lifecycle status. active = payable now; processing = a payment is being confirmed; paid = settled; inactive = deactivated.",
                            "enum": [
                              "active",
                              "processing",
                              "paid",
                              "inactive",
                              "needs_replacement",
                              "action_required"
                            ]
                          },
                          "provider": {
                            "type": "string",
                            "description": "Payment provider hosting the checkout.",
                            "enum": [
                              "stripe",
                              "wix"
                            ]
                          },
                          "mode": {
                            "type": "string",
                            "description": "Provider environment.",
                            "enum": [
                              "test",
                              "live"
                            ]
                          },
                          "amount_minor": {
                            "type": "integer",
                            "description": "Amount the link collects. Integer amount in the currency's MINOR unit (e.g. cents)."
                          },
                          "currency": {
                            "type": "string",
                            "description": "Link currency. ISO 4217 alphabetic code, e.g. \"USD\".",
                            "minLength": 3,
                            "maxLength": 3
                          },
                          "url": {
                            "type": "string",
                            "description": "Hosted customer-facing pay-page URL. Treat as a capability: anyone with the URL can open the checkout.",
                            "nullable": true
                          },
                          "paid_at": {
                            "type": "string",
                            "description": "When the link was paid (ISO 8601 UTC).",
                            "nullable": true,
                            "format": "date-time"
                          },
                          "paid_amount_minor": {
                            "type": "integer",
                            "description": "Settled amount. Integer amount in the currency's MINOR unit (e.g. cents).",
                            "nullable": true
                          },
                          "paid_currency": {
                            "type": "string",
                            "description": "Settled currency. ISO 4217 alphabetic code, e.g. \"USD\".",
                            "nullable": true,
                            "minLength": 3,
                            "maxLength": 3
                          },
                          "deactivated_at": {
                            "type": "string",
                            "description": "When the link was deactivated (ISO 8601 UTC).",
                            "nullable": true,
                            "format": "date-time"
                          },
                          "created_at": {
                            "type": "string",
                            "description": "Creation time (ISO 8601 UTC).",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "description": "Last update time (ISO 8601 UTC).",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "invoice_id",
                          "installment_id",
                          "status",
                          "provider",
                          "mode",
                          "amount_minor",
                          "currency",
                          "url",
                          "paid_at",
                          "paid_amount_minor",
                          "paid_currency",
                          "deactivated_at",
                          "created_at",
                          "updated_at"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "next_cursor": {
                          "type": "string",
                          "description": "Opaque cursor for the next page — pass as ?cursor=. Null when there are no further results.",
                          "nullable": true
                        },
                        "has_more": {
                          "type": "boolean",
                          "description": "Whether another page exists."
                        },
                        "limit": {
                          "type": "integer",
                          "description": "The page size that was applied."
                        }
                      },
                      "required": [
                        "next_cursor",
                        "has_more",
                        "limit"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "data": [
                    {
                      "id": "0d9c8b7a-6e5f-4d3c-b2a1-908f7e6d5c4b",
                      "invoice_id": "8f14e45f-ceea-4a5b-9d2c-167ce7de1a10",
                      "installment_id": "a3c9d2e1-55b4-4c8e-9f01-2b3c4d5e6f70",
                      "status": "active",
                      "provider": "stripe",
                      "mode": "live",
                      "amount_minor": 125000,
                      "currency": "USD",
                      "url": "https://app.getfinero.com/pay/EXAMPLEtokenEXAMPLEtokenEXAMPLEtokenEXAMPL",
                      "paid_at": null,
                      "paid_amount_minor": null,
                      "paid_currency": null,
                      "deactivated_at": null,
                      "created_at": "2026-07-10T06:31:00+00:00",
                      "updated_at": "2026-07-10T06:31:00+00:00"
                    }
                  ],
                  "pagination": {
                    "next_cursor": null,
                    "has_more": false,
                    "limit": 25
                  }
                }
              }
            }
          },
          "400": {
            "description": "`validation_failed` — The request failed schema validation. See error.details for field messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_credentials` — The credential is malformed or does not match an active key. The response never reveals whether a key identifier exists. `missing_credentials` — No Authorization: Bearer header was sent. `revoked_credentials` — The presented key was revoked. Create a new key in Settings → API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`feature_unavailable` — The tenant's tier does not currently include API access. Feature rollout flags cannot grant this entitlement. `permission_denied` — The key's permission does not allow this operation (readonly keys cannot call admin-only operations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No such resource in YOUR tenant. Ids belonging to another tenant are indistinguishable from missing ones.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limit_exceeded` — Too many requests. Honor the Retry-After header (seconds) before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Unexpected server error. Safe to retry with the same Idempotency-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createPaymentLink",
        "summary": "Create a payment link for an installment",
        "description": "Creates a hosted payment link for one collectible installment through the tenant's connected payment provider — the same validation and creation path the Finero app uses. At most ONE active link can exist per installment: a duplicate attempt returns 409 conflict. Requires the Idempotency-Key header; retrying with the same key and body returns the original result. If the tenant's email-notification workflow is enabled, the customer payment-link email is sent as a consequence.\n\nRequires an **admin** API key (has side effects).",
        "tags": [
          "Payment links"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-permission": "admin",
        "x-side-effects": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Unique key (1–255 printable ASCII chars) scoping safe retries. Same key + same body replays the original result; same key + different body returns 409 idempotency_conflict.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Payment link id.",
                      "format": "uuid"
                    },
                    "invoice_id": {
                      "type": "string",
                      "description": "Invoice the link collects.",
                      "format": "uuid"
                    },
                    "installment_id": {
                      "type": "string",
                      "description": "Installment the link collects (one active link per installment).",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "description": "Lifecycle status. active = payable now; processing = a payment is being confirmed; paid = settled; inactive = deactivated.",
                      "enum": [
                        "active",
                        "processing",
                        "paid",
                        "inactive",
                        "needs_replacement",
                        "action_required"
                      ]
                    },
                    "provider": {
                      "type": "string",
                      "description": "Payment provider hosting the checkout.",
                      "enum": [
                        "stripe",
                        "wix"
                      ]
                    },
                    "mode": {
                      "type": "string",
                      "description": "Provider environment.",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "amount_minor": {
                      "type": "integer",
                      "description": "Amount the link collects. Integer amount in the currency's MINOR unit (e.g. cents)."
                    },
                    "currency": {
                      "type": "string",
                      "description": "Link currency. ISO 4217 alphabetic code, e.g. \"USD\".",
                      "minLength": 3,
                      "maxLength": 3
                    },
                    "url": {
                      "type": "string",
                      "description": "Hosted customer-facing pay-page URL. Treat as a capability: anyone with the URL can open the checkout.",
                      "nullable": true
                    },
                    "paid_at": {
                      "type": "string",
                      "description": "When the link was paid (ISO 8601 UTC).",
                      "nullable": true,
                      "format": "date-time"
                    },
                    "paid_amount_minor": {
                      "type": "integer",
                      "description": "Settled amount. Integer amount in the currency's MINOR unit (e.g. cents).",
                      "nullable": true
                    },
                    "paid_currency": {
                      "type": "string",
                      "description": "Settled currency. ISO 4217 alphabetic code, e.g. \"USD\".",
                      "nullable": true,
                      "minLength": 3,
                      "maxLength": 3
                    },
                    "deactivated_at": {
                      "type": "string",
                      "description": "When the link was deactivated (ISO 8601 UTC).",
                      "nullable": true,
                      "format": "date-time"
                    },
                    "created_at": {
                      "type": "string",
                      "description": "Creation time (ISO 8601 UTC).",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "description": "Last update time (ISO 8601 UTC).",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "invoice_id",
                    "installment_id",
                    "status",
                    "provider",
                    "mode",
                    "amount_minor",
                    "currency",
                    "url",
                    "paid_at",
                    "paid_amount_minor",
                    "paid_currency",
                    "deactivated_at",
                    "created_at",
                    "updated_at"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "id": "0d9c8b7a-6e5f-4d3c-b2a1-908f7e6d5c4b",
                  "invoice_id": "8f14e45f-ceea-4a5b-9d2c-167ce7de1a10",
                  "installment_id": "a3c9d2e1-55b4-4c8e-9f01-2b3c4d5e6f70",
                  "status": "active",
                  "provider": "stripe",
                  "mode": "live",
                  "amount_minor": 125000,
                  "currency": "USD",
                  "url": "https://app.getfinero.com/pay/EXAMPLEtokenEXAMPLEtokenEXAMPLEtokenEXAMPL",
                  "paid_at": null,
                  "paid_amount_minor": null,
                  "paid_currency": null,
                  "deactivated_at": null,
                  "created_at": "2026-07-10T06:31:00+00:00",
                  "updated_at": "2026-07-10T06:31:00+00:00"
                }
              }
            }
          },
          "400": {
            "description": "`idempotency_key_required` — This operation requires the Idempotency-Key header (1–255 characters). `validation_failed` — The request failed schema validation. See error.details for field messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_credentials` — The credential is malformed or does not match an active key. The response never reveals whether a key identifier exists. `missing_credentials` — No Authorization: Bearer header was sent. `revoked_credentials` — The presented key was revoked. Create a new key in Settings → API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`feature_unavailable` — The tenant's tier does not currently include API access. Feature rollout flags cannot grant this entitlement. `permission_denied` — The key's permission does not allow this operation (readonly keys cannot call admin-only operations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No such resource in YOUR tenant. Ids belonging to another tenant are indistinguishable from missing ones.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`conflict` — State conflict — e.g. an active payment link already exists for the installment, a sync is already running, or a concurrent identical request is in flight. `idempotency_conflict` — The Idempotency-Key was already used with a DIFFERENT request body. Use a fresh key for a new request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large` — The request body exceeds the 64 KiB limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`currency_not_supported` — The selected payment provider cannot collect the invoice's currency (single-currency providers can only charge in their store currency). Use a provider that supports it, or collect the invoice outside Finero. `installment_not_collectible` — The installment cannot be collected right now — it is already paid, disputed, excluded from collections, has no outstanding balance, or its invoice is not collection-ready. GET /v1/invoices/{invoiceId} reports each installment's payment_state, collectible flag, and block reasons. `integration_unavailable` — The tenant has no connected payments integration able to mint this link, or the one selected is not ready. Connect or repair a payment provider in the Finero app, then retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limit_exceeded` — Too many requests. Honor the Retry-After header (seconds) before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Unexpected server error. Safe to retry with the same Idempotency-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "invoice_id": {
                    "type": "string",
                    "description": "Invoice to collect.",
                    "format": "uuid"
                  },
                  "installment_id": {
                    "type": "string",
                    "description": "Installment to collect (from GET /v1/invoices/{invoiceId}).",
                    "format": "uuid"
                  },
                  "integration_id": {
                    "type": "string",
                    "description": "Optional: a specific connected payments integration. Defaults to the tenant's connected provider.",
                    "format": "uuid"
                  }
                },
                "required": [
                  "invoice_id",
                  "installment_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "invoice_id": "8f14e45f-ceea-4a5b-9d2c-167ce7de1a10",
                "installment_id": "a3c9d2e1-55b4-4c8e-9f01-2b3c4d5e6f70"
              }
            }
          }
        }
      }
    },
    "/v1/payment-links/{paymentLinkId}": {
      "get": {
        "operationId": "getPaymentLink",
        "summary": "Get a payment link",
        "description": "Returns one payment link, including its hosted checkout URL and settlement state.\n\nAvailable to **admin and readonly** API keys (no side effects).",
        "tags": [
          "Payment links"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-permission": "readonly",
        "x-side-effects": false,
        "parameters": [
          {
            "name": "paymentLinkId",
            "in": "path",
            "required": true,
            "description": "Payment link id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Payment link id.",
                      "format": "uuid"
                    },
                    "invoice_id": {
                      "type": "string",
                      "description": "Invoice the link collects.",
                      "format": "uuid"
                    },
                    "installment_id": {
                      "type": "string",
                      "description": "Installment the link collects (one active link per installment).",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "description": "Lifecycle status. active = payable now; processing = a payment is being confirmed; paid = settled; inactive = deactivated.",
                      "enum": [
                        "active",
                        "processing",
                        "paid",
                        "inactive",
                        "needs_replacement",
                        "action_required"
                      ]
                    },
                    "provider": {
                      "type": "string",
                      "description": "Payment provider hosting the checkout.",
                      "enum": [
                        "stripe",
                        "wix"
                      ]
                    },
                    "mode": {
                      "type": "string",
                      "description": "Provider environment.",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "amount_minor": {
                      "type": "integer",
                      "description": "Amount the link collects. Integer amount in the currency's MINOR unit (e.g. cents)."
                    },
                    "currency": {
                      "type": "string",
                      "description": "Link currency. ISO 4217 alphabetic code, e.g. \"USD\".",
                      "minLength": 3,
                      "maxLength": 3
                    },
                    "url": {
                      "type": "string",
                      "description": "Hosted customer-facing pay-page URL. Treat as a capability: anyone with the URL can open the checkout.",
                      "nullable": true
                    },
                    "paid_at": {
                      "type": "string",
                      "description": "When the link was paid (ISO 8601 UTC).",
                      "nullable": true,
                      "format": "date-time"
                    },
                    "paid_amount_minor": {
                      "type": "integer",
                      "description": "Settled amount. Integer amount in the currency's MINOR unit (e.g. cents).",
                      "nullable": true
                    },
                    "paid_currency": {
                      "type": "string",
                      "description": "Settled currency. ISO 4217 alphabetic code, e.g. \"USD\".",
                      "nullable": true,
                      "minLength": 3,
                      "maxLength": 3
                    },
                    "deactivated_at": {
                      "type": "string",
                      "description": "When the link was deactivated (ISO 8601 UTC).",
                      "nullable": true,
                      "format": "date-time"
                    },
                    "created_at": {
                      "type": "string",
                      "description": "Creation time (ISO 8601 UTC).",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "description": "Last update time (ISO 8601 UTC).",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "invoice_id",
                    "installment_id",
                    "status",
                    "provider",
                    "mode",
                    "amount_minor",
                    "currency",
                    "url",
                    "paid_at",
                    "paid_amount_minor",
                    "paid_currency",
                    "deactivated_at",
                    "created_at",
                    "updated_at"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "id": "0d9c8b7a-6e5f-4d3c-b2a1-908f7e6d5c4b",
                  "invoice_id": "8f14e45f-ceea-4a5b-9d2c-167ce7de1a10",
                  "installment_id": "a3c9d2e1-55b4-4c8e-9f01-2b3c4d5e6f70",
                  "status": "active",
                  "provider": "stripe",
                  "mode": "live",
                  "amount_minor": 125000,
                  "currency": "USD",
                  "url": "https://app.getfinero.com/pay/EXAMPLEtokenEXAMPLEtokenEXAMPLEtokenEXAMPL",
                  "paid_at": null,
                  "paid_amount_minor": null,
                  "paid_currency": null,
                  "deactivated_at": null,
                  "created_at": "2026-07-10T06:31:00+00:00",
                  "updated_at": "2026-07-10T06:31:00+00:00"
                }
              }
            }
          },
          "400": {
            "description": "`validation_failed` — The request failed schema validation. See error.details for field messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_credentials` — The credential is malformed or does not match an active key. The response never reveals whether a key identifier exists. `missing_credentials` — No Authorization: Bearer header was sent. `revoked_credentials` — The presented key was revoked. Create a new key in Settings → API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`feature_unavailable` — The tenant's tier does not currently include API access. Feature rollout flags cannot grant this entitlement. `permission_denied` — The key's permission does not allow this operation (readonly keys cannot call admin-only operations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No such resource in YOUR tenant. Ids belonging to another tenant are indistinguishable from missing ones.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limit_exceeded` — Too many requests. Honor the Retry-After header (seconds) before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Unexpected server error. Safe to retry with the same Idempotency-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/payment-links/{paymentLinkId}/deactivate": {
      "post": {
        "operationId": "deactivatePaymentLink",
        "summary": "Deactivate a payment link",
        "description": "Immediately deactivates an active payment link so it can no longer be paid. Naturally idempotent: deactivating an already-inactive link succeeds without change. A paid link cannot be deactivated (409 conflict).\n\nRequires an **admin** API key (has side effects).",
        "tags": [
          "Payment links"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-permission": "admin",
        "x-side-effects": true,
        "parameters": [
          {
            "name": "paymentLinkId",
            "in": "path",
            "required": true,
            "description": "Payment link id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Payment link id.",
                      "format": "uuid"
                    },
                    "invoice_id": {
                      "type": "string",
                      "description": "Invoice the link collects.",
                      "format": "uuid"
                    },
                    "installment_id": {
                      "type": "string",
                      "description": "Installment the link collects (one active link per installment).",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "description": "Lifecycle status. active = payable now; processing = a payment is being confirmed; paid = settled; inactive = deactivated.",
                      "enum": [
                        "active",
                        "processing",
                        "paid",
                        "inactive",
                        "needs_replacement",
                        "action_required"
                      ]
                    },
                    "provider": {
                      "type": "string",
                      "description": "Payment provider hosting the checkout.",
                      "enum": [
                        "stripe",
                        "wix"
                      ]
                    },
                    "mode": {
                      "type": "string",
                      "description": "Provider environment.",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "amount_minor": {
                      "type": "integer",
                      "description": "Amount the link collects. Integer amount in the currency's MINOR unit (e.g. cents)."
                    },
                    "currency": {
                      "type": "string",
                      "description": "Link currency. ISO 4217 alphabetic code, e.g. \"USD\".",
                      "minLength": 3,
                      "maxLength": 3
                    },
                    "url": {
                      "type": "string",
                      "description": "Hosted customer-facing pay-page URL. Treat as a capability: anyone with the URL can open the checkout.",
                      "nullable": true
                    },
                    "paid_at": {
                      "type": "string",
                      "description": "When the link was paid (ISO 8601 UTC).",
                      "nullable": true,
                      "format": "date-time"
                    },
                    "paid_amount_minor": {
                      "type": "integer",
                      "description": "Settled amount. Integer amount in the currency's MINOR unit (e.g. cents).",
                      "nullable": true
                    },
                    "paid_currency": {
                      "type": "string",
                      "description": "Settled currency. ISO 4217 alphabetic code, e.g. \"USD\".",
                      "nullable": true,
                      "minLength": 3,
                      "maxLength": 3
                    },
                    "deactivated_at": {
                      "type": "string",
                      "description": "When the link was deactivated (ISO 8601 UTC).",
                      "nullable": true,
                      "format": "date-time"
                    },
                    "created_at": {
                      "type": "string",
                      "description": "Creation time (ISO 8601 UTC).",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "description": "Last update time (ISO 8601 UTC).",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "invoice_id",
                    "installment_id",
                    "status",
                    "provider",
                    "mode",
                    "amount_minor",
                    "currency",
                    "url",
                    "paid_at",
                    "paid_amount_minor",
                    "paid_currency",
                    "deactivated_at",
                    "created_at",
                    "updated_at"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "id": "0d9c8b7a-6e5f-4d3c-b2a1-908f7e6d5c4b",
                  "invoice_id": "8f14e45f-ceea-4a5b-9d2c-167ce7de1a10",
                  "installment_id": "a3c9d2e1-55b4-4c8e-9f01-2b3c4d5e6f70",
                  "status": "inactive",
                  "provider": "stripe",
                  "mode": "live",
                  "amount_minor": 125000,
                  "currency": "USD",
                  "url": null,
                  "paid_at": null,
                  "paid_amount_minor": null,
                  "paid_currency": null,
                  "deactivated_at": "2026-07-14T07:10:00+00:00",
                  "created_at": "2026-07-10T06:31:00+00:00",
                  "updated_at": "2026-07-10T06:31:00+00:00"
                }
              }
            }
          },
          "400": {
            "description": "`validation_failed` — The request failed schema validation. See error.details for field messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_credentials` — The credential is malformed or does not match an active key. The response never reveals whether a key identifier exists. `missing_credentials` — No Authorization: Bearer header was sent. `revoked_credentials` — The presented key was revoked. Create a new key in Settings → API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`feature_unavailable` — The tenant's tier does not currently include API access. Feature rollout flags cannot grant this entitlement. `permission_denied` — The key's permission does not allow this operation (readonly keys cannot call admin-only operations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No such resource in YOUR tenant. Ids belonging to another tenant are indistinguishable from missing ones.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`conflict` — State conflict — e.g. an active payment link already exists for the installment, a sync is already running, or a concurrent identical request is in flight.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large` — The request body exceeds the 64 KiB limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limit_exceeded` — Too many requests. Honor the Retry-After header (seconds) before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Unexpected server error. Safe to retry with the same Idempotency-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/payments": {
      "get": {
        "operationId": "listPayments",
        "summary": "List confirmed payments",
        "description": "Lists confirmed payments, newest first. A payment appears here only after the payment provider verifies settlement; payments cannot be created through the API. Cursor-paginated: pass ?limit= (1–100) and follow pagination.next_cursor until has_more is false.\n\nAvailable to **admin and readonly** API keys (no side effects).",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-permission": "readonly",
        "x-side-effects": false,
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100. Default 25.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque pagination cursor from a previous response's pagination.next_cursor.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 400
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort direction by creation time. Default desc (newest first).",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "invoice_id",
            "in": "query",
            "required": false,
            "description": "Filter by invoice.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "payment_link_id",
            "in": "query",
            "required": false,
            "description": "Filter by payment link.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "provider",
            "in": "query",
            "required": false,
            "description": "Filter by provider.",
            "schema": {
              "type": "string",
              "enum": [
                "stripe",
                "wix"
              ]
            }
          },
          {
            "name": "confirmed_since",
            "in": "query",
            "required": false,
            "description": "Only payments confirmed at or after this ISO 8601 timestamp.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "description": "Confirmed payments, newest first.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Payment id.",
                            "format": "uuid"
                          },
                          "invoice_id": {
                            "type": "string",
                            "description": "Invoice the payment settles.",
                            "format": "uuid"
                          },
                          "installment_id": {
                            "type": "string",
                            "description": "Installment the payment settles.",
                            "format": "uuid"
                          },
                          "payment_link_id": {
                            "type": "string",
                            "description": "The payment link the payer used.",
                            "format": "uuid"
                          },
                          "provider": {
                            "type": "string",
                            "description": "Payment provider.",
                            "enum": [
                              "stripe",
                              "wix"
                            ]
                          },
                          "provider_payment_id": {
                            "type": "string",
                            "description": "The provider's payment/transaction identifier."
                          },
                          "amount_minor": {
                            "type": "integer",
                            "description": "Settled amount. Integer amount in the currency's MINOR unit (e.g. cents)."
                          },
                          "currency": {
                            "type": "string",
                            "description": "Settled currency. ISO 4217 alphabetic code, e.g. \"USD\".",
                            "minLength": 3,
                            "maxLength": 3
                          },
                          "mode": {
                            "type": "string",
                            "description": "Provider environment.",
                            "enum": [
                              "test",
                              "live"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "description": "Payments appear here only once verified/confirmed by the provider.",
                            "enum": [
                              "confirmed"
                            ]
                          },
                          "provider_paid_at": {
                            "type": "string",
                            "description": "Provider-reported payment time (ISO 8601 UTC).",
                            "nullable": true,
                            "format": "date-time"
                          },
                          "confirmed_at": {
                            "type": "string",
                            "description": "When Finero confirmed the settlement (ISO 8601 UTC).",
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "invoice_id",
                          "installment_id",
                          "payment_link_id",
                          "provider",
                          "provider_payment_id",
                          "amount_minor",
                          "currency",
                          "mode",
                          "status",
                          "provider_paid_at",
                          "confirmed_at"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "next_cursor": {
                          "type": "string",
                          "description": "Opaque cursor for the next page — pass as ?cursor=. Null when there are no further results.",
                          "nullable": true
                        },
                        "has_more": {
                          "type": "boolean",
                          "description": "Whether another page exists."
                        },
                        "limit": {
                          "type": "integer",
                          "description": "The page size that was applied."
                        }
                      },
                      "required": [
                        "next_cursor",
                        "has_more",
                        "limit"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "data": [
                    {
                      "id": "7e6d5c4b-3a29-4180-9f8e-7d6c5b4a3928",
                      "invoice_id": "8f14e45f-ceea-4a5b-9d2c-167ce7de1a10",
                      "installment_id": "a3c9d2e1-55b4-4c8e-9f01-2b3c4d5e6f70",
                      "payment_link_id": "0d9c8b7a-6e5f-4d3c-b2a1-908f7e6d5c4b",
                      "provider": "stripe",
                      "provider_payment_id": "pi_ExampleOnly000000000000",
                      "amount_minor": 125000,
                      "currency": "USD",
                      "mode": "live",
                      "status": "confirmed",
                      "provider_paid_at": "2026-07-11T14:02:11+00:00",
                      "confirmed_at": "2026-07-11T14:02:14+00:00"
                    }
                  ],
                  "pagination": {
                    "next_cursor": null,
                    "has_more": false,
                    "limit": 25
                  }
                }
              }
            }
          },
          "400": {
            "description": "`validation_failed` — The request failed schema validation. See error.details for field messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_credentials` — The credential is malformed or does not match an active key. The response never reveals whether a key identifier exists. `missing_credentials` — No Authorization: Bearer header was sent. `revoked_credentials` — The presented key was revoked. Create a new key in Settings → API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`feature_unavailable` — The tenant's tier does not currently include API access. Feature rollout flags cannot grant this entitlement. `permission_denied` — The key's permission does not allow this operation (readonly keys cannot call admin-only operations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No such resource in YOUR tenant. Ids belonging to another tenant are indistinguishable from missing ones.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limit_exceeded` — Too many requests. Honor the Retry-After header (seconds) before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Unexpected server error. Safe to retry with the same Idempotency-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/payments/{paymentId}": {
      "get": {
        "operationId": "getPayment",
        "summary": "Get a payment",
        "description": "Returns one confirmed payment by id, including the provider reference and settlement timestamps.\n\nAvailable to **admin and readonly** API keys (no side effects).",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-permission": "readonly",
        "x-side-effects": false,
        "parameters": [
          {
            "name": "paymentId",
            "in": "path",
            "required": true,
            "description": "Payment id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Payment id.",
                      "format": "uuid"
                    },
                    "invoice_id": {
                      "type": "string",
                      "description": "Invoice the payment settles.",
                      "format": "uuid"
                    },
                    "installment_id": {
                      "type": "string",
                      "description": "Installment the payment settles.",
                      "format": "uuid"
                    },
                    "payment_link_id": {
                      "type": "string",
                      "description": "The payment link the payer used.",
                      "format": "uuid"
                    },
                    "provider": {
                      "type": "string",
                      "description": "Payment provider.",
                      "enum": [
                        "stripe",
                        "wix"
                      ]
                    },
                    "provider_payment_id": {
                      "type": "string",
                      "description": "The provider's payment/transaction identifier."
                    },
                    "amount_minor": {
                      "type": "integer",
                      "description": "Settled amount. Integer amount in the currency's MINOR unit (e.g. cents)."
                    },
                    "currency": {
                      "type": "string",
                      "description": "Settled currency. ISO 4217 alphabetic code, e.g. \"USD\".",
                      "minLength": 3,
                      "maxLength": 3
                    },
                    "mode": {
                      "type": "string",
                      "description": "Provider environment.",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "description": "Payments appear here only once verified/confirmed by the provider.",
                      "enum": [
                        "confirmed"
                      ]
                    },
                    "provider_paid_at": {
                      "type": "string",
                      "description": "Provider-reported payment time (ISO 8601 UTC).",
                      "nullable": true,
                      "format": "date-time"
                    },
                    "confirmed_at": {
                      "type": "string",
                      "description": "When Finero confirmed the settlement (ISO 8601 UTC).",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "invoice_id",
                    "installment_id",
                    "payment_link_id",
                    "provider",
                    "provider_payment_id",
                    "amount_minor",
                    "currency",
                    "mode",
                    "status",
                    "provider_paid_at",
                    "confirmed_at"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "id": "7e6d5c4b-3a29-4180-9f8e-7d6c5b4a3928",
                  "invoice_id": "8f14e45f-ceea-4a5b-9d2c-167ce7de1a10",
                  "installment_id": "a3c9d2e1-55b4-4c8e-9f01-2b3c4d5e6f70",
                  "payment_link_id": "0d9c8b7a-6e5f-4d3c-b2a1-908f7e6d5c4b",
                  "provider": "stripe",
                  "provider_payment_id": "pi_ExampleOnly000000000000",
                  "amount_minor": 125000,
                  "currency": "USD",
                  "mode": "live",
                  "status": "confirmed",
                  "provider_paid_at": "2026-07-11T14:02:11+00:00",
                  "confirmed_at": "2026-07-11T14:02:14+00:00"
                }
              }
            }
          },
          "400": {
            "description": "`validation_failed` — The request failed schema validation. See error.details for field messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_credentials` — The credential is malformed or does not match an active key. The response never reveals whether a key identifier exists. `missing_credentials` — No Authorization: Bearer header was sent. `revoked_credentials` — The presented key was revoked. Create a new key in Settings → API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`feature_unavailable` — The tenant's tier does not currently include API access. Feature rollout flags cannot grant this entitlement. `permission_denied` — The key's permission does not allow this operation (readonly keys cannot call admin-only operations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No such resource in YOUR tenant. Ids belonging to another tenant are indistinguishable from missing ones.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limit_exceeded` — Too many requests. Honor the Retry-After header (seconds) before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Unexpected server error. Safe to retry with the same Idempotency-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sync-runs": {
      "get": {
        "operationId": "listSyncRuns",
        "summary": "List sync runs",
        "description": "Lists ERP sync runs, newest first. Cursor-paginated: pass ?limit= (1–100) and follow pagination.next_cursor until has_more is false.\n\nAvailable to **admin and readonly** API keys (no side effects).",
        "tags": [
          "ERP sync"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-permission": "readonly",
        "x-side-effects": false,
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100. Default 25.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque pagination cursor from a previous response's pagination.next_cursor.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 400
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort direction by creation time. Default desc (newest first).",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "connection_id",
            "in": "query",
            "required": false,
            "description": "Filter by ERP connection.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by run status.",
            "schema": {
              "type": "string",
              "enum": [
                "running",
                "success",
                "partial",
                "failed"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "Filter by sync direction. Only `pull` matches new runs; `push` matches historical runs recorded before outbound sync was removed.",
            "schema": {
              "type": "string",
              "enum": [
                "pull",
                "push"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "description": "Sync runs, newest first.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Sync run id — poll GET /v1/sync-runs/{syncRunId} until status is terminal.",
                            "format": "uuid"
                          },
                          "connection_id": {
                            "type": "string",
                            "description": "The ERP connection that ran.",
                            "format": "uuid"
                          },
                          "direction": {
                            "type": "string",
                            "description": "Sync direction. Every new run is `pull` — Finero no longer writes invoices back to an ERP. `push` remains in the enum because historical runs carry it.",
                            "enum": [
                              "pull",
                              "push"
                            ]
                          },
                          "trigger": {
                            "type": "string",
                            "description": "How the run started. API-started runs are `manual`.",
                            "enum": [
                              "manual",
                              "scheduled"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "description": "running is non-terminal; success/partial/failed are terminal.",
                            "enum": [
                              "running",
                              "success",
                              "partial",
                              "failed"
                            ]
                          },
                          "started_at": {
                            "type": "string",
                            "description": "Run start time (ISO 8601 UTC).",
                            "format": "date-time"
                          },
                          "finished_at": {
                            "type": "string",
                            "description": "Run finish time (null while running) (ISO 8601 UTC).",
                            "nullable": true,
                            "format": "date-time"
                          },
                          "items_processed": {
                            "type": "integer",
                            "description": "Invoices processed."
                          },
                          "items_failed": {
                            "type": "integer",
                            "description": "Invoices that failed to process."
                          },
                          "error_summary": {
                            "type": "string",
                            "description": "Categorical failure summary (never raw provider payloads).",
                            "nullable": true
                          }
                        },
                        "required": [
                          "id",
                          "connection_id",
                          "direction",
                          "trigger",
                          "status",
                          "started_at",
                          "finished_at",
                          "items_processed",
                          "items_failed",
                          "error_summary"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "next_cursor": {
                          "type": "string",
                          "description": "Opaque cursor for the next page — pass as ?cursor=. Null when there are no further results.",
                          "nullable": true
                        },
                        "has_more": {
                          "type": "boolean",
                          "description": "Whether another page exists."
                        },
                        "limit": {
                          "type": "integer",
                          "description": "The page size that was applied."
                        }
                      },
                      "required": [
                        "next_cursor",
                        "has_more",
                        "limit"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "data": [
                    {
                      "id": "3f2e1d0c-9b8a-4756-8493-21f0e9d8c7b6",
                      "connection_id": "1a2b3c4d-5e6f-4a80-91b2-c3d4e5f60718",
                      "direction": "pull",
                      "trigger": "manual",
                      "status": "success",
                      "started_at": "2026-07-14T05:00:04+00:00",
                      "finished_at": "2026-07-14T05:00:41+00:00",
                      "items_processed": 36,
                      "items_failed": 0,
                      "error_summary": null
                    }
                  ],
                  "pagination": {
                    "next_cursor": null,
                    "has_more": false,
                    "limit": 25
                  }
                }
              }
            }
          },
          "400": {
            "description": "`validation_failed` — The request failed schema validation. See error.details for field messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_credentials` — The credential is malformed or does not match an active key. The response never reveals whether a key identifier exists. `missing_credentials` — No Authorization: Bearer header was sent. `revoked_credentials` — The presented key was revoked. Create a new key in Settings → API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`feature_unavailable` — The tenant's tier does not currently include API access. Feature rollout flags cannot grant this entitlement. `permission_denied` — The key's permission does not allow this operation (readonly keys cannot call admin-only operations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No such resource in YOUR tenant. Ids belonging to another tenant are indistinguishable from missing ones.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limit_exceeded` — Too many requests. Honor the Retry-After header (seconds) before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Unexpected server error. Safe to retry with the same Idempotency-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sync-runs/{syncRunId}": {
      "get": {
        "operationId": "getSyncRun",
        "summary": "Get a sync run",
        "description": "Returns one ERP sync run (read-only operational visibility). Syncs are started from within Finero — the public API cannot trigger synchronization.\n\nAvailable to **admin and readonly** API keys (no side effects).",
        "tags": [
          "ERP sync"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-permission": "readonly",
        "x-side-effects": false,
        "parameters": [
          {
            "name": "syncRunId",
            "in": "path",
            "required": true,
            "description": "Sync run id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Sync run id — poll GET /v1/sync-runs/{syncRunId} until status is terminal.",
                      "format": "uuid"
                    },
                    "connection_id": {
                      "type": "string",
                      "description": "The ERP connection that ran.",
                      "format": "uuid"
                    },
                    "direction": {
                      "type": "string",
                      "description": "Sync direction. Every new run is `pull` — Finero no longer writes invoices back to an ERP. `push` remains in the enum because historical runs carry it.",
                      "enum": [
                        "pull",
                        "push"
                      ]
                    },
                    "trigger": {
                      "type": "string",
                      "description": "How the run started. API-started runs are `manual`.",
                      "enum": [
                        "manual",
                        "scheduled"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "description": "running is non-terminal; success/partial/failed are terminal.",
                      "enum": [
                        "running",
                        "success",
                        "partial",
                        "failed"
                      ]
                    },
                    "started_at": {
                      "type": "string",
                      "description": "Run start time (ISO 8601 UTC).",
                      "format": "date-time"
                    },
                    "finished_at": {
                      "type": "string",
                      "description": "Run finish time (null while running) (ISO 8601 UTC).",
                      "nullable": true,
                      "format": "date-time"
                    },
                    "items_processed": {
                      "type": "integer",
                      "description": "Invoices processed."
                    },
                    "items_failed": {
                      "type": "integer",
                      "description": "Invoices that failed to process."
                    },
                    "error_summary": {
                      "type": "string",
                      "description": "Categorical failure summary (never raw provider payloads).",
                      "nullable": true
                    }
                  },
                  "required": [
                    "id",
                    "connection_id",
                    "direction",
                    "trigger",
                    "status",
                    "started_at",
                    "finished_at",
                    "items_processed",
                    "items_failed",
                    "error_summary"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "id": "3f2e1d0c-9b8a-4756-8493-21f0e9d8c7b6",
                  "connection_id": "1a2b3c4d-5e6f-4a80-91b2-c3d4e5f60718",
                  "direction": "pull",
                  "trigger": "manual",
                  "status": "success",
                  "started_at": "2026-07-14T05:00:04+00:00",
                  "finished_at": "2026-07-14T05:00:41+00:00",
                  "items_processed": 36,
                  "items_failed": 0,
                  "error_summary": null
                }
              }
            }
          },
          "400": {
            "description": "`validation_failed` — The request failed schema validation. See error.details for field messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_credentials` — The credential is malformed or does not match an active key. The response never reveals whether a key identifier exists. `missing_credentials` — No Authorization: Bearer header was sent. `revoked_credentials` — The presented key was revoked. Create a new key in Settings → API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`feature_unavailable` — The tenant's tier does not currently include API access. Feature rollout flags cannot grant this entitlement. `permission_denied` — The key's permission does not allow this operation (readonly keys cannot call admin-only operations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No such resource in YOUR tenant. Ids belonging to another tenant are indistinguishable from missing ones.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limit_exceeded` — Too many requests. Honor the Retry-After header (seconds) before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Unexpected server error. Safe to retry with the same Idempotency-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workflow-executions": {
      "get": {
        "operationId": "listWorkflowExecutions",
        "summary": "List workflow executions",
        "description": "Audit trail of the tenant's automation runs (payment-link automation + email notification): what ran, what was skipped, and why — with stable machine-readable reason codes. Cursor-paginated: pass ?limit= (1–100) and follow pagination.next_cursor until has_more is false.\n\nAvailable to **admin and readonly** API keys (no side effects).",
        "tags": [
          "Workflows"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-permission": "readonly",
        "x-side-effects": false,
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1–100. Default 25.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque pagination cursor from a previous response's pagination.next_cursor.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 400
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort direction by creation time. Default desc (newest first).",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "workflow_type",
            "in": "query",
            "required": false,
            "description": "Filter by workflow type.",
            "schema": {
              "type": "string",
              "enum": [
                "payment_link_automation",
                "email_notification"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by outcome.",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "processing",
                "succeeded",
                "skipped",
                "failed"
              ]
            }
          },
          {
            "name": "invoice_id",
            "in": "query",
            "required": false,
            "description": "Filter by invoice.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "description": "Workflow executions, newest first.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Workflow execution id.",
                            "format": "uuid"
                          },
                          "workflow_type": {
                            "type": "string",
                            "description": "Which automation ran.",
                            "enum": [
                              "payment_link_automation",
                              "email_notification"
                            ]
                          },
                          "invoice_id": {
                            "type": "string",
                            "description": "Related invoice.",
                            "nullable": true,
                            "format": "uuid"
                          },
                          "installment_id": {
                            "type": "string",
                            "description": "Related installment.",
                            "nullable": true,
                            "format": "uuid"
                          },
                          "payment_link_id": {
                            "type": "string",
                            "description": "Related payment link.",
                            "nullable": true,
                            "format": "uuid"
                          },
                          "trigger_reason": {
                            "type": "string",
                            "description": "What triggered the run."
                          },
                          "status": {
                            "type": "string",
                            "description": "Outcome of this execution.",
                            "enum": [
                              "pending",
                              "processing",
                              "succeeded",
                              "skipped",
                              "failed"
                            ]
                          },
                          "reason_code": {
                            "type": "string",
                            "description": "Categorical reason for a skip/failure (stable machine-readable code).",
                            "nullable": true
                          },
                          "reason_message": {
                            "type": "string",
                            "description": "Human-readable reason.",
                            "nullable": true
                          },
                          "started_at": {
                            "type": "string",
                            "description": "Execution start (ISO 8601 UTC).",
                            "format": "date-time"
                          },
                          "finished_at": {
                            "type": "string",
                            "description": "Execution finish (ISO 8601 UTC).",
                            "nullable": true,
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "workflow_type",
                          "invoice_id",
                          "installment_id",
                          "payment_link_id",
                          "trigger_reason",
                          "status",
                          "reason_code",
                          "reason_message",
                          "started_at",
                          "finished_at"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "next_cursor": {
                          "type": "string",
                          "description": "Opaque cursor for the next page — pass as ?cursor=. Null when there are no further results.",
                          "nullable": true
                        },
                        "has_more": {
                          "type": "boolean",
                          "description": "Whether another page exists."
                        },
                        "limit": {
                          "type": "integer",
                          "description": "The page size that was applied."
                        }
                      },
                      "required": [
                        "next_cursor",
                        "has_more",
                        "limit"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "data": [
                    {
                      "id": "6c5d4e3f-2a1b-4c9d-8e7f-0a1b2c3d4e5f",
                      "workflow_type": "payment_link_automation",
                      "invoice_id": "8f14e45f-ceea-4a5b-9d2c-167ce7de1a10",
                      "installment_id": "a3c9d2e1-55b4-4c8e-9f01-2b3c4d5e6f70",
                      "payment_link_id": "0d9c8b7a-6e5f-4d3c-b2a1-908f7e6d5c4b",
                      "trigger_reason": "invoice_synced",
                      "status": "succeeded",
                      "reason_code": null,
                      "reason_message": null,
                      "started_at": "2026-07-10T06:31:00+00:00",
                      "finished_at": "2026-07-10T06:31:01+00:00"
                    }
                  ],
                  "pagination": {
                    "next_cursor": null,
                    "has_more": false,
                    "limit": 25
                  }
                }
              }
            }
          },
          "400": {
            "description": "`validation_failed` — The request failed schema validation. See error.details for field messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_credentials` — The credential is malformed or does not match an active key. The response never reveals whether a key identifier exists. `missing_credentials` — No Authorization: Bearer header was sent. `revoked_credentials` — The presented key was revoked. Create a new key in Settings → API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`feature_unavailable` — The tenant's tier does not currently include API access. Feature rollout flags cannot grant this entitlement. `permission_denied` — The key's permission does not allow this operation (readonly keys cannot call admin-only operations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — No such resource in YOUR tenant. Ids belonging to another tenant are indistinguishable from missing ones.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limit_exceeded` — Too many requests. Honor the Retry-After header (seconds) before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error` — Unexpected server error. Safe to retry with the same Idempotency-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "A Finero API key (`fnr_…`), created by a tenant admin in Settings → API. Sent as `Authorization: Bearer fnr_…`. Never place keys in URLs."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "request_id"
            ],
            "additionalProperties": false,
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable error code.",
                "enum": [
                  "missing_credentials",
                  "invalid_credentials",
                  "revoked_credentials",
                  "feature_unavailable",
                  "permission_denied",
                  "not_found",
                  "validation_failed",
                  "conflict",
                  "idempotency_key_required",
                  "idempotency_conflict",
                  "rate_limit_exceeded",
                  "payload_too_large",
                  "method_not_allowed",
                  "installment_not_collectible",
                  "integration_unavailable",
                  "currency_not_supported",
                  "internal_error"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation."
              },
              "request_id": {
                "type": "string",
                "description": "Echo of X-Request-Id — include it when reporting problems."
              },
              "details": {
                "type": "array",
                "description": "Field-level validation messages (validation_failed only).",
                "items": {
                  "type": "object",
                  "required": [
                    "field",
                    "message"
                  ],
                  "properties": {
                    "field": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}