{
  "openapi": "3.0.3",
  "info": {
    "title": "Flux ERP integration API (v1)",
    "version": "1",
    "description": "Customers, invoices, receipts, signed links, checkout and webhooks. Generated from the Flux developer docs source. Authenticate with `Authorization: ApiKey flx_live_…`."
  },
  "servers": [
    {
      "url": "{baseUrl}",
      "variables": {
        "baseUrl": {
          "default": "https://erpapi.osigla.com.ng",
          "description": "Your environment's API base URL"
        }
      }
    }
  ],
  "security": [
    {
      "ApiKey": []
    }
  ],
  "tags": [
    {
      "name": "Customers"
    },
    {
      "name": "Invoices"
    },
    {
      "name": "Receipts"
    },
    {
      "name": "Webhook endpoints"
    }
  ],
  "paths": {
    "/api/v1/customers": {
      "post": {
        "tags": [
          "Customers"
        ],
        "operationId": "createCustomer",
        "summary": "Create a customer",
        "description": "Creates a customer. Send an Idempotency-Key so a retry can't create a second one.\n\nScope: `customers:write`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "example": {
                  "id": 812,
                  "externalRef": "ws_42",
                  "customerType": "company",
                  "name": "Leadway Group Plc",
                  "legalName": "Leadway Group Plc",
                  "companyName": "Leadway Group",
                  "firstName": null,
                  "lastName": null,
                  "email": "finance@leadway.example",
                  "phone": "+2348012345678",
                  "address": "121 Funso Williams Ave, Lagos",
                  "taxId": "12345678-0001",
                  "vatNumber": "VAT-001",
                  "rcNumber": "RC123456",
                  "parentCustomerId": null,
                  "regulatedEntityCount": 3,
                  "defaultCurrency": "NGN",
                  "defaultSettlementBankId": null,
                  "contacts": [
                    {
                      "name": "Finance",
                      "email": "finance@leadway.example",
                      "phone": null,
                      "isBillingPrimary": true,
                      "ccOnInvoices": false
                    },
                    {
                      "name": "CFO",
                      "email": "cfo@leadway.example",
                      "phone": null,
                      "isBillingPrimary": false,
                      "ccOnInvoices": true
                    }
                  ],
                  "createdAt": "2026-11-01T10:20:00",
                  "updatedAt": null
                }
              }
            }
          },
          "400": {
            "description": "Error: `invalid_request`, `invalid_idempotency_key`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error: `conflict`, `idempotency_in_progress`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error: `idempotency_key_reused`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerInput"
              },
              "example": {
                "customerType": "company",
                "companyName": "Leadway Group",
                "legalName": "Leadway Group Plc",
                "email": "finance@leadway.example",
                "phone": "+2348012345678",
                "address": "121 Funso Williams Ave, Lagos",
                "taxId": "12345678-0001",
                "vatNumber": "VAT-001",
                "rcNumber": "RC123456",
                "externalRef": "ws_42",
                "regulatedEntityCount": 3,
                "defaultCurrency": "NGN",
                "contacts": [
                  {
                    "name": "Finance",
                    "email": "finance@leadway.example",
                    "isBillingPrimary": true,
                    "ccOnInvoices": false
                  },
                  {
                    "name": "CFO",
                    "email": "cfo@leadway.example",
                    "isBillingPrimary": false,
                    "ccOnInvoices": true
                  }
                ]
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Customers"
        ],
        "operationId": "listCustomers",
        "summary": "List customers",
        "description": "Lists customers, newest first. Filter by externalRef to find the customer you created for one of your records. List results have contacts: null; get the customer to read its contacts.\n\nScope: `customers:read`.",
        "parameters": [
          {
            "name": "externalRef",
            "in": "query",
            "required": false,
            "description": "Only the customer with this reference.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page, 1 to 100. Default 25.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The nextCursor from the previous page. Leave it out for the first page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Customer"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": 812,
                      "externalRef": "ws_42",
                      "customerType": "company",
                      "name": "Leadway Group Plc",
                      "legalName": "Leadway Group Plc",
                      "companyName": "Leadway Group",
                      "firstName": null,
                      "lastName": null,
                      "email": "finance@leadway.example",
                      "phone": "+2348012345678",
                      "address": "121 Funso Williams Ave, Lagos",
                      "taxId": "12345678-0001",
                      "vatNumber": "VAT-001",
                      "rcNumber": "RC123456",
                      "parentCustomerId": null,
                      "regulatedEntityCount": 3,
                      "defaultCurrency": "NGN",
                      "defaultSettlementBankId": null,
                      "contacts": null,
                      "createdAt": "2026-11-01T10:20:00",
                      "updatedAt": null
                    }
                  ],
                  "nextCursor": null,
                  "hasMore": false
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/customers/{id}": {
      "patch": {
        "tags": [
          "Customers"
        ],
        "operationId": "updateCustomer",
        "summary": "Update a customer",
        "description": "Changes only the fields you send. Sending contacts replaces the whole list; leave it out to keep the current contacts.\n\nScope: `customers:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The customer's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "example": {
                  "id": 812,
                  "externalRef": "ws_42",
                  "customerType": "company",
                  "name": "Leadway Group Plc",
                  "legalName": "Leadway Group Plc",
                  "companyName": "Leadway Group",
                  "firstName": null,
                  "lastName": null,
                  "email": "finance@leadway.example",
                  "phone": "+2348098765432",
                  "address": "121 Funso Williams Ave, Lagos",
                  "taxId": "12345678-0001",
                  "vatNumber": "VAT-001",
                  "rcNumber": "RC123456",
                  "parentCustomerId": null,
                  "regulatedEntityCount": 4,
                  "defaultCurrency": "NGN",
                  "defaultSettlementBankId": null,
                  "contacts": [
                    {
                      "name": "Finance",
                      "email": "finance@leadway.example",
                      "phone": null,
                      "isBillingPrimary": true,
                      "ccOnInvoices": false
                    },
                    {
                      "name": "CFO",
                      "email": "cfo@leadway.example",
                      "phone": null,
                      "isBillingPrimary": false,
                      "ccOnInvoices": true
                    }
                  ],
                  "createdAt": "2026-11-01T10:20:00",
                  "updatedAt": "2026-11-08T16:45:00"
                }
              }
            }
          },
          "400": {
            "description": "Error: `invalid_request`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error: `conflict`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerInput"
              },
              "example": {
                "phone": "+2348098765432",
                "regulatedEntityCount": 4
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Customers"
        ],
        "operationId": "getCustomer",
        "summary": "Get a customer",
        "description": "Returns the customer with its billing contacts.\n\nScope: `customers:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The customer's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "example": {
                  "id": 812,
                  "externalRef": "ws_42",
                  "customerType": "company",
                  "name": "Leadway Group Plc",
                  "legalName": "Leadway Group Plc",
                  "companyName": "Leadway Group",
                  "firstName": null,
                  "lastName": null,
                  "email": "finance@leadway.example",
                  "phone": "+2348012345678",
                  "address": "121 Funso Williams Ave, Lagos",
                  "taxId": "12345678-0001",
                  "vatNumber": "VAT-001",
                  "rcNumber": "RC123456",
                  "parentCustomerId": null,
                  "regulatedEntityCount": 3,
                  "defaultCurrency": "NGN",
                  "defaultSettlementBankId": null,
                  "contacts": [
                    {
                      "name": "Finance",
                      "email": "finance@leadway.example",
                      "phone": null,
                      "isBillingPrimary": true,
                      "ccOnInvoices": false
                    },
                    {
                      "name": "CFO",
                      "email": "cfo@leadway.example",
                      "phone": null,
                      "isBillingPrimary": false,
                      "ccOnInvoices": true
                    }
                  ],
                  "createdAt": "2026-11-01T10:20:00",
                  "updatedAt": null
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/customers/{id}/enrollments": {
      "get": {
        "tags": [
          "Customers"
        ],
        "operationId": "listEnrollments",
        "summary": "List a customer's enrollments",
        "description": "The services the customer is enrolled in: their current plan. Returns an array, newest first, not a page.\n\nScope: `customers:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The customer's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Enrollment"
                  }
                },
                "example": [
                  {
                    "id": 31,
                    "serviceId": 7,
                    "serviceName": "Subscription",
                    "amount": 250000,
                    "enrolledFrom": "2026-10-01T00:00:00",
                    "enrolledTo": null,
                    "active": true
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/customers/{id}/statement": {
      "get": {
        "tags": [
          "Customers"
        ],
        "operationId": "getStatement",
        "summary": "Get a customer's statement",
        "description": "Open, overdue and paid totals per currency, for a billing page or a credit check. Counts approved and paid invoices; voided ones are excluded.\n\nScope: `customers:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The customer's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Statement"
                },
                "example": {
                  "customerId": 812,
                  "currencies": [
                    {
                      "currency": "NGN",
                      "open": 500000,
                      "overdue": 250000,
                      "paid": 1250000,
                      "openCount": 2,
                      "overdueCount": 1,
                      "paidCount": 5
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/invoices": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "operationId": "createInvoice",
        "summary": "Raise an invoice",
        "description": "Raises an invoice into the organization's approval workflow, as if a person had raised it in the app. It's recorded as raised by the admin who created the API key. The customer isn't emailed until the invoice is approved.\n\nScope: `invoices:write`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "201": {
            "description": "Created: a new invoice",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                },
                "example": {
                  "id": 5012,
                  "number": "MAX-2026-0042",
                  "externalRef": "ws_42-2026-11",
                  "customerId": 812,
                  "currency": "NGN",
                  "status": "awaiting_approval",
                  "overdue": false,
                  "paymentStatus": null,
                  "invoiceType": "subscription",
                  "periodStart": "2026-11-01T00:00:00",
                  "periodEnd": "2026-11-30T00:00:00",
                  "nextDueDate": "2026-12-01T00:00:00",
                  "createdAt": "2026-11-01T09:00:00",
                  "approvedAt": null,
                  "dueDate": "2026-11-15T00:00:00",
                  "paidAt": null,
                  "subtotal": 800000,
                  "discount": 0,
                  "taxableNet": 800000,
                  "vatAmount": 60000,
                  "vatLabel": "VAT (7.5%)",
                  "total": 860000,
                  "lines": [
                    {
                      "serviceId": 7,
                      "description": "Subscription — 3 entities (3 × 250,000.00)",
                      "detail": "3 entities",
                      "quantity": 3,
                      "unitPrice": 250000,
                      "amount": 750000
                    },
                    {
                      "serviceId": 9,
                      "description": "Onboarding",
                      "detail": null,
                      "quantity": 1,
                      "unitPrice": 50000,
                      "amount": 50000
                    }
                  ],
                  "payments": null
                }
              }
            }
          },
          "200": {
            "description": "OK: an invoice with this externalRef already exists",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                },
                "example": {
                  "id": 5012,
                  "number": "MAX-2026-0042",
                  "externalRef": "ws_42-2026-11",
                  "customerId": 812,
                  "currency": "NGN",
                  "status": "approved",
                  "overdue": false,
                  "paymentStatus": null,
                  "invoiceType": "subscription",
                  "periodStart": "2026-11-01T00:00:00",
                  "periodEnd": "2026-11-30T00:00:00",
                  "nextDueDate": "2026-12-01T00:00:00",
                  "createdAt": "2026-11-01T09:00:00",
                  "approvedAt": "2026-11-01T11:30:00",
                  "dueDate": "2026-11-15T00:00:00",
                  "paidAt": null,
                  "subtotal": 800000,
                  "discount": 0,
                  "taxableNet": 800000,
                  "vatAmount": 60000,
                  "vatLabel": "VAT (7.5%)",
                  "total": 860000,
                  "lines": [
                    {
                      "serviceId": 7,
                      "description": "Subscription — 3 entities (3 × 250,000.00)",
                      "detail": "3 entities",
                      "quantity": 3,
                      "unitPrice": 250000,
                      "amount": 750000
                    },
                    {
                      "serviceId": 9,
                      "description": "Onboarding",
                      "detail": null,
                      "quantity": 1,
                      "unitPrice": 50000,
                      "amount": 50000
                    }
                  ],
                  "payments": null
                }
              }
            }
          },
          "400": {
            "description": "Error: `invalid_request`, `invalid_idempotency_key`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error: `idempotency_in_progress`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error: `idempotency_key_reused`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateInvoiceRequest"
              },
              "example": {
                "externalRef": "ws_42-2026-11",
                "customerId": 812,
                "currency": "NGN",
                "dueDate": "2026-11-15",
                "discount": 0,
                "periodStart": "2026-11-01",
                "periodEnd": "2026-11-30",
                "invoiceType": "subscription",
                "nextDueDate": "2026-12-01",
                "comments": "November subscription",
                "lines": [
                  {
                    "serviceId": 7,
                    "quantity": 3,
                    "unitPrice": 250000,
                    "detail": "3 entities"
                  },
                  {
                    "serviceId": 9,
                    "amount": 50000
                  }
                ]
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Invoices"
        ],
        "operationId": "listInvoices",
        "summary": "List invoices",
        "description": "Lists invoices, newest first. Filters combine.\n\nScope: `invoices:read`.",
        "parameters": [
          {
            "name": "customerId",
            "in": "query",
            "required": false,
            "description": "Only this customer's invoices.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only invoices with this status. An unknown value gets 400 invalid_request.",
            "schema": {
              "type": "string",
              "enum": [
                "awaiting_approval",
                "awaiting_final_approval",
                "returned",
                "approved",
                "paid",
                "voided"
              ]
            }
          },
          {
            "name": "overdue",
            "in": "query",
            "required": false,
            "description": "true for only overdue invoices.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "externalRef",
            "in": "query",
            "required": false,
            "description": "Only the invoice with this reference.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page, 1 to 100. Default 25.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The nextCursor from the previous page. Leave it out for the first page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Invoice"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": 5012,
                      "number": "MAX-2026-0042",
                      "externalRef": "ws_42-2026-11",
                      "customerId": 812,
                      "currency": "NGN",
                      "status": "approved",
                      "overdue": false,
                      "paymentStatus": null,
                      "invoiceType": "subscription",
                      "periodStart": "2026-11-01T00:00:00",
                      "periodEnd": "2026-11-30T00:00:00",
                      "nextDueDate": "2026-12-01T00:00:00",
                      "createdAt": "2026-11-01T09:00:00",
                      "approvedAt": "2026-11-01T11:30:00",
                      "dueDate": "2026-11-15T00:00:00",
                      "paidAt": null,
                      "subtotal": 800000,
                      "discount": 0,
                      "taxableNet": 800000,
                      "vatAmount": 60000,
                      "vatLabel": "VAT (7.5%)",
                      "total": 860000,
                      "lines": [
                        {
                          "serviceId": 7,
                          "description": "Subscription — 3 entities (3 × 250,000.00)",
                          "detail": "3 entities",
                          "quantity": 3,
                          "unitPrice": 250000,
                          "amount": 750000
                        },
                        {
                          "serviceId": 9,
                          "description": "Onboarding",
                          "detail": null,
                          "quantity": 1,
                          "unitPrice": 50000,
                          "amount": 50000
                        }
                      ],
                      "payments": null
                    }
                  ],
                  "nextCursor": null,
                  "hasMore": false
                }
              }
            }
          },
          "400": {
            "description": "Error: `invalid_request`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/invoices/{id}": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "operationId": "getInvoice",
        "summary": "Get an invoice",
        "description": "Returns the invoice with its lines, period, type and status. With payments:read, it also includes the payment attempts.\n\nScope: `invoices:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The invoice's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                },
                "example": {
                  "id": 5012,
                  "number": "MAX-2026-0042",
                  "externalRef": "ws_42-2026-11",
                  "customerId": 812,
                  "currency": "NGN",
                  "status": "approved",
                  "overdue": false,
                  "paymentStatus": null,
                  "invoiceType": "subscription",
                  "periodStart": "2026-11-01T00:00:00",
                  "periodEnd": "2026-11-30T00:00:00",
                  "nextDueDate": "2026-12-01T00:00:00",
                  "createdAt": "2026-11-01T09:00:00",
                  "approvedAt": "2026-11-01T11:30:00",
                  "dueDate": "2026-11-15T00:00:00",
                  "paidAt": null,
                  "subtotal": 800000,
                  "discount": 0,
                  "taxableNet": 800000,
                  "vatAmount": 60000,
                  "vatLabel": "VAT (7.5%)",
                  "total": 860000,
                  "lines": [
                    {
                      "serviceId": 7,
                      "description": "Subscription — 3 entities (3 × 250,000.00)",
                      "detail": "3 entities",
                      "quantity": 3,
                      "unitPrice": 250000,
                      "amount": 750000
                    },
                    {
                      "serviceId": 9,
                      "description": "Onboarding",
                      "detail": null,
                      "quantity": 1,
                      "unitPrice": 50000,
                      "amount": 50000
                    }
                  ],
                  "payments": [
                    {
                      "reference": "T12-MAX-2026-0042-K3P9QZ_1",
                      "channel": "paystack",
                      "succeeded": false,
                      "attemptedAt": "2026-11-02T08:15:00"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/invoices/{id}/links": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "operationId": "invoiceLinks",
        "summary": "Create invoice links",
        "description": "Returns signed links to the invoice PDF and its payment page, safe to show to your customer. payUrl is null when the invoice is paid or voided. Ask for fresh links each time you show them. See Signed links.\n\nScope: `invoices:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The invoice's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Links"
                },
                "example": {
                  "pdfUrl": "https://api.flux.example/public/invoices/NDIuNTAxMi5pcC4xNzk2MTE2NDAw.mJb2Kc0r1oB7yWq4sZ0eD9xVh3tLnA6uPfRkGiE8ScQ/pdf",
                  "payUrl": "https://api.flux.example/public/invoices/NDIuNTAxMi5wYXkuMTc5NjExNjQwMA.Q2pXv8nTz1sLcR0aB4yHk7uWm3eJd6gFoNiV5qYtKxE/pay",
                  "expiresAt": "2026-12-01T10:00:00.5236411Z"
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error: `links_not_configured`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/invoices/{id}/payments": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "operationId": "listPayments",
        "summary": "List payment attempts",
        "description": "The invoice's online payment attempts, as an array. An attempt that didn't succeed may still be in progress: wait for the webhook.\n\nScope: `payments:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The invoice's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Payment"
                  }
                },
                "example": [
                  {
                    "reference": "T12-MAX-2026-0042-K3P9QZ_1",
                    "channel": "paystack",
                    "succeeded": false,
                    "attemptedAt": "2026-11-02T08:15:00"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/invoices/{id}/checkout": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "operationId": "checkout",
        "summary": "Start a checkout",
        "description": "Starts an online payment with Paystack and returns the checkout URL. Redirect the payer there. The redirect back from Paystack isn't proof of payment: wait for invoice.paid. See Payments.\n\nScope: `payments:write`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The invoice's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkout"
                },
                "example": {
                  "checkoutUrl": "https://checkout.paystack.com/7xk2m9q4fz0b1ac"
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error: `invoice_voided`, `already_paid`, `not_online`, `payment_not_available`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Error: `gateway_error`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/receipts/{invoiceId}": {
      "get": {
        "tags": [
          "Receipts"
        ],
        "operationId": "getReceipt",
        "summary": "Get a receipt",
        "description": "Returns the receipt for a paid invoice.\n\nScope: `invoices:read`.",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "description": "The paid invoice's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Receipt"
                },
                "example": {
                  "invoiceId": 5012,
                  "invoiceNumber": "MAX-2026-0042",
                  "customerId": 812,
                  "currency": "NGN",
                  "amount": 860000,
                  "paidAt": "2026-11-03T14:02:00",
                  "paymentMethod": "online"
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`, `not_paid`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/receipts/{invoiceId}/links": {
      "post": {
        "tags": [
          "Receipts"
        ],
        "operationId": "receiptLinks",
        "summary": "Create a receipt link",
        "description": "Returns a signed link to the receipt PDF. payUrl is always null here.\n\nScope: `invoices:read`.",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "description": "The paid invoice's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Links"
                },
                "example": {
                  "pdfUrl": "https://api.flux.example/public/receipts/NDIuNTAxMi5ycC4xNzk2MTE2NDAw.Zt4hQ1vB8kW0mXc3sR7eLp2yN6uGaJ9oFdKiT5wHqEs/pdf",
                  "payUrl": null,
                  "expiresAt": "2026-12-01T10:00:00.5236411Z"
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`, `not_paid`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error: `links_not_configured`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhook-endpoints": {
      "get": {
        "tags": [
          "Webhook endpoints"
        ],
        "operationId": "listEndpoints",
        "summary": "List endpoints",
        "description": "All the organization's webhook endpoints, as an array, oldest first.\n\nScope: `webhooks:manage`.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WebhookEndpoint"
                  }
                },
                "example": [
                  {
                    "id": 3,
                    "url": "https://hooks.example.com/flux",
                    "description": "Billing sync",
                    "events": [
                      "invoice.paid",
                      "receipt.issued"
                    ],
                    "active": true,
                    "disabledReason": null,
                    "consecutiveFailures": 0,
                    "createdAt": "2026-11-01T10:00:00",
                    "secret": null
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Webhook endpoints"
        ],
        "operationId": "createEndpoint",
        "summary": "Register an endpoint",
        "description": "Registers a URL to receive events. The response includes the signing secret. It's shown once: store it in your secret manager straight away.\n\nScope: `webhooks:manage`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                },
                "example": {
                  "id": 3,
                  "url": "https://hooks.example.com/flux",
                  "description": "Billing sync",
                  "events": [
                    "invoice.paid",
                    "receipt.issued"
                  ],
                  "active": true,
                  "disabledReason": null,
                  "consecutiveFailures": 0,
                  "createdAt": "2026-11-01T10:00:00",
                  "secret": "whsec_Xq3vT8mKp2Lr7Ns1Wd5Yb9Hc4Fj6Ga0Z"
                }
              }
            }
          },
          "400": {
            "description": "Error: `invalid_request`, `invalid_idempotency_key`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error: `idempotency_in_progress`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error: `idempotency_key_reused`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointInput"
              },
              "example": {
                "url": "https://hooks.example.com/flux",
                "description": "Billing sync",
                "events": [
                  "invoice.paid",
                  "receipt.issued"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/webhook-endpoints/{id}": {
      "get": {
        "tags": [
          "Webhook endpoints"
        ],
        "operationId": "getEndpoint",
        "summary": "Get an endpoint",
        "description": "Returns one endpoint. secret is null.\n\nScope: `webhooks:manage`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The webhook endpoint's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                },
                "example": {
                  "id": 3,
                  "url": "https://hooks.example.com/flux",
                  "description": "Billing sync",
                  "events": [
                    "invoice.paid",
                    "receipt.issued"
                  ],
                  "active": true,
                  "disabledReason": null,
                  "consecutiveFailures": 0,
                  "createdAt": "2026-11-01T10:00:00",
                  "secret": null
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Webhook endpoints"
        ],
        "operationId": "updateEndpoint",
        "summary": "Update an endpoint",
        "description": "Changes the URL, events, description or active state. Setting active: true re-enables an endpoint Flux switched off, and resets its failure count.\n\nScope: `webhooks:manage`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The webhook endpoint's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                },
                "example": {
                  "id": 3,
                  "url": "https://hooks.example.com/flux",
                  "description": "Billing sync",
                  "events": [
                    "invoice.paid",
                    "receipt.issued"
                  ],
                  "active": true,
                  "disabledReason": null,
                  "consecutiveFailures": 0,
                  "createdAt": "2026-11-01T10:00:00",
                  "secret": null
                }
              }
            }
          },
          "400": {
            "description": "Error: `invalid_request`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointInput"
              },
              "example": {
                "active": true
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Webhook endpoints"
        ],
        "operationId": "deleteEndpoint",
        "summary": "Delete an endpoint",
        "description": "Deletes the endpoint and cancels its pending deliveries.\n\nScope: `webhooks:manage`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The webhook endpoint's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhook-endpoints/{id}/rotate-secret": {
      "post": {
        "tags": [
          "Webhook endpoints"
        ],
        "operationId": "rotateSecret",
        "summary": "Rotate the signing secret",
        "description": "Creates a new signing secret and returns it once. The old secret stops working immediately, so deploy the new one promptly. See Rotate keys and secrets.\n\nScope: `webhooks:manage`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The webhook endpoint's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                },
                "example": {
                  "id": 3,
                  "url": "https://hooks.example.com/flux",
                  "description": "Billing sync",
                  "events": [
                    "invoice.paid",
                    "receipt.issued"
                  ],
                  "active": true,
                  "disabledReason": null,
                  "consecutiveFailures": 0,
                  "createdAt": "2026-11-01T10:00:00",
                  "secret": "whsec_P7nR2kV9cX4mT1bQ8wL5yJ3hD6gF0sZa"
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhook-endpoints/{id}/test": {
      "post": {
        "tags": [
          "Webhook endpoints"
        ],
        "operationId": "testEndpoint",
        "summary": "Send a test event",
        "description": "Queues a webhook.test event to this endpoint only. It's signed and retried like any other event.\n\nScope: `webhooks:manage`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The webhook endpoint's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted"
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhook-endpoints/{id}/deliveries": {
      "get": {
        "tags": [
          "Webhook endpoints"
        ],
        "operationId": "listDeliveries",
        "summary": "List deliveries",
        "description": "The endpoint's delivery log, newest first.\n\nScope: `webhooks:manage`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The webhook endpoint's id.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page, 1 to 100. Default 25.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The nextCursor from the previous page. Leave it out for the first page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Delivery"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": 99001,
                      "eventId": "evt_01JC8Z5T6Q1W3M4N5P6R7S8T9V",
                      "eventType": "invoice.paid",
                      "status": "delivered",
                      "attempts": 1,
                      "lastStatusCode": 200,
                      "lastError": null,
                      "nextAttemptAt": null,
                      "lastAttemptAt": "2026-11-03T14:02:05",
                      "deliveredAt": "2026-11-03T14:02:05",
                      "createdAt": "2026-11-03T14:02:04"
                    }
                  ],
                  "nextCursor": null,
                  "hasMore": false
                }
              }
            }
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhook-deliveries/{id}/redeliver": {
      "post": {
        "tags": [
          "Webhook endpoints"
        ],
        "operationId": "redeliver",
        "summary": "Redeliver",
        "description": "Sends a delivery again now, whatever its status, keeping its attempt history. Use it for dead deliveries once your endpoint is fixed.\n\nScope: `webhooks:manage`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The delivery's id, from the delivery log.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted"
          },
          "401": {
            "description": "Error: `unauthorized`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error: `insufficient_scope`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: `not_found`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: `rate_limited`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "The value is `ApiKey ` followed by your key, such as `ApiKey flx_live_…`."
      }
    },
    "schemas": {
      "Customer": {
        "type": "object",
        "description": "Someone the organization bills: an individual or a company.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32",
            "description": "Flux's id for the customer."
          },
          "externalRef": {
            "type": "string",
            "nullable": true,
            "description": "Your id for the customer, such as a workspace id. Unique within the organization."
          },
          "customerType": {
            "type": "string",
            "enum": [
              "individual",
              "company"
            ],
            "description": "individual or company."
          },
          "name": {
            "type": "string",
            "description": "The billing name. For a company, the legal name, else the company name. For an individual, their name."
          },
          "legalName": {
            "type": "string",
            "nullable": true,
            "description": "Registered legal name."
          },
          "companyName": {
            "type": "string",
            "nullable": true,
            "description": "Trading name. Set for companies."
          },
          "firstName": {
            "type": "string",
            "nullable": true,
            "description": "Set for individuals."
          },
          "lastName": {
            "type": "string",
            "nullable": true,
            "description": "Set for individuals."
          },
          "email": {
            "type": "string",
            "description": "The customer's email. Gets invoices when there's no primary contact."
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "description": "In international format."
          },
          "address": {
            "type": "string",
            "nullable": true,
            "description": "Postal address."
          },
          "taxId": {
            "type": "string",
            "nullable": true,
            "description": "Tax identification number (TIN). Unique within the organization."
          },
          "vatNumber": {
            "type": "string",
            "nullable": true,
            "description": "VAT registration number."
          },
          "rcNumber": {
            "type": "string",
            "nullable": true,
            "description": "CAC registration (RC) number."
          },
          "parentCustomerId": {
            "type": "integer",
            "format": "int32",
            "nullable": true,
            "description": "The group company this customer belongs to, in the same organization."
          },
          "regulatedEntityCount": {
            "type": "integer",
            "format": "int32",
            "nullable": true,
            "description": "Number of regulated entities. 0 or more."
          },
          "defaultCurrency": {
            "type": "string",
            "enum": [
              "NGN",
              "USD",
              "CAD",
              "EUR",
              "GBP",
              null
            ],
            "nullable": true,
            "description": "Pre-fills the currency when someone raises an invoice for this customer in the Flux app."
          },
          "defaultSettlementBankId": {
            "type": "integer",
            "format": "int32",
            "nullable": true,
            "description": "The organization's bank account that this customer's payments settle into. Ids come from the Flux app."
          },
          "contacts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomerContact"
            },
            "nullable": true,
            "description": "Billing contacts. Returned by get, create and update. Always null in list results."
          },
          "createdAt": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?$",
            "description": "When the customer was created. Server time, no offset."
          },
          "updatedAt": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?$",
            "description": "When the customer last changed. null if never.",
            "nullable": true
          }
        },
        "required": [
          "id",
          "externalRef",
          "customerType",
          "name",
          "legalName",
          "companyName",
          "firstName",
          "lastName",
          "email",
          "phone",
          "address",
          "taxId",
          "vatNumber",
          "rcNumber",
          "parentCustomerId",
          "regulatedEntityCount",
          "defaultCurrency",
          "defaultSettlementBankId",
          "contacts",
          "createdAt",
          "updatedAt"
        ]
      },
      "CustomerContact": {
        "type": "object",
        "description": "An email on a customer that receives invoices and receipts.",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Label or person's name."
          },
          "email": {
            "type": "string",
            "description": "Receives invoices and receipts."
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "description": "Phone number."
          },
          "isBillingPrimary": {
            "type": "boolean",
            "description": "The primary billing contact. At most one per customer."
          },
          "ccOnInvoices": {
            "type": "boolean",
            "description": "Copied on invoice and receipt emails."
          }
        },
        "required": [
          "name",
          "email",
          "phone",
          "isBillingPrimary",
          "ccOnInvoices"
        ]
      },
      "CustomerInput": {
        "type": "object",
        "description": "Body for creating or updating a customer. On update, every field is optional and only the fields you send change.",
        "properties": {
          "customerType": {
            "type": "string",
            "enum": [
              "individual",
              "company"
            ],
            "description": "individual or company. Defaults to individual on create."
          },
          "email": {
            "type": "string",
            "description": "A valid email address. Required on create."
          },
          "firstName": {
            "type": "string",
            "description": "Required for individuals."
          },
          "lastName": {
            "type": "string",
            "description": "Required for individuals."
          },
          "phone": {
            "type": "string",
            "description": "Required and must be valid for individuals. Optional for companies, but must be valid if sent."
          },
          "companyName": {
            "type": "string",
            "description": "Required for companies."
          },
          "legalName": {
            "type": "string",
            "description": "Registered legal name. Used as the billing name when set."
          },
          "address": {
            "type": "string",
            "description": "Postal address."
          },
          "taxId": {
            "type": "string",
            "description": "TIN. Another customer in the organization can't have the same one."
          },
          "vatNumber": {
            "type": "string",
            "description": "VAT registration number."
          },
          "rcNumber": {
            "type": "string",
            "description": "CAC registration (RC) number."
          },
          "externalRef": {
            "type": "string",
            "description": "Your id for the customer. At most 100 characters, unique within the organization."
          },
          "parentCustomerId": {
            "type": "integer",
            "format": "int32",
            "description": "A customer in the same organization. Can't be the customer itself, or a customer whose parent is this one."
          },
          "regulatedEntityCount": {
            "type": "integer",
            "format": "int32",
            "description": "0 or more."
          },
          "defaultCurrency": {
            "type": "string",
            "enum": [
              "NGN",
              "USD",
              "CAD",
              "EUR",
              "GBP"
            ],
            "description": "A supported currency code."
          },
          "defaultSettlementBankId": {
            "type": "integer",
            "format": "int32",
            "description": "One of the organization's bank account ids, from the Flux app."
          },
          "contacts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactInput"
            },
            "description": "At most 20, each with a valid email, and at most one primary. On update, sending contacts replaces the whole list. Leave it out to keep the current contacts."
          }
        },
        "required": [
          "email"
        ]
      },
      "ContactInput": {
        "type": "object",
        "description": "A billing contact sent on create or update.",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Label or person's name."
          },
          "email": {
            "type": "string",
            "description": "A valid email address."
          },
          "phone": {
            "type": "string",
            "nullable": true,
            "description": "Phone number."
          },
          "isBillingPrimary": {
            "type": "boolean",
            "description": "Make this the primary contact. At most one contact can be primary. Default false."
          },
          "ccOnInvoices": {
            "type": "boolean",
            "description": "Copy this contact on invoice and receipt emails. Default true when omitted."
          }
        },
        "required": [
          "email"
        ]
      },
      "Enrollment": {
        "type": "object",
        "description": "A customer's subscription to a service in the organization's catalogue.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32",
            "description": "Enrollment id."
          },
          "serviceId": {
            "type": "integer",
            "format": "int32",
            "description": "The service in the organization's catalogue."
          },
          "serviceName": {
            "type": "string",
            "nullable": true,
            "description": "The service's name."
          },
          "amount": {
            "type": "number",
            "format": "decimal",
            "description": "The agreed amount, else the service's price. Major units."
          },
          "enrolledFrom": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T00:00:00$",
            "description": "Start date."
          },
          "enrolledTo": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T00:00:00$",
            "description": "End date. null if open-ended.",
            "nullable": true
          },
          "active": {
            "type": "boolean",
            "description": "Active and within its dates today."
          }
        },
        "required": [
          "id",
          "serviceId",
          "serviceName",
          "amount",
          "enrolledFrom",
          "enrolledTo",
          "active"
        ]
      },
      "Statement": {
        "type": "object",
        "description": "Open, overdue and paid totals per currency. Counts approved and paid invoices; voided ones are excluded.",
        "properties": {
          "customerId": {
            "type": "integer",
            "format": "int32",
            "description": "The customer."
          },
          "currencies": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CurrencyTotals"
            },
            "description": "One entry per currency the customer has invoices in. Empty if none."
          }
        },
        "required": [
          "customerId",
          "currencies"
        ]
      },
      "CurrencyTotals": {
        "type": "object",
        "description": "Totals for one currency.",
        "properties": {
          "currency": {
            "type": "string",
            "enum": [
              "NGN",
              "USD",
              "CAD",
              "EUR",
              "GBP"
            ],
            "description": "ISO currency code."
          },
          "open": {
            "type": "number",
            "format": "decimal",
            "description": "Total of approved, unpaid invoices. Includes the overdue ones."
          },
          "overdue": {
            "type": "number",
            "format": "decimal",
            "description": "Total of open invoices past their due date."
          },
          "paid": {
            "type": "number",
            "format": "decimal",
            "description": "Total of paid invoices."
          },
          "openCount": {
            "type": "integer",
            "format": "int32",
            "description": "Number of open invoices."
          },
          "overdueCount": {
            "type": "integer",
            "format": "int32",
            "description": "Number of overdue invoices."
          },
          "paidCount": {
            "type": "integer",
            "format": "int32",
            "description": "Number of paid invoices."
          }
        },
        "required": [
          "currency",
          "open",
          "overdue",
          "paid",
          "openCount",
          "overdueCount",
          "paidCount"
        ]
      },
      "Invoice": {
        "type": "object",
        "description": "An invoice raised into the organization's approval workflow.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32",
            "description": "Flux's id for the invoice."
          },
          "number": {
            "type": "string",
            "description": "The invoice number. Its format depends on the organization's settings: treat it as an opaque string."
          },
          "externalRef": {
            "type": "string",
            "nullable": true,
            "description": "Your reference, if you sent one."
          },
          "customerId": {
            "type": "integer",
            "format": "int32",
            "description": "The customer billed."
          },
          "currency": {
            "type": "string",
            "enum": [
              "NGN",
              "USD",
              "CAD",
              "EUR",
              "GBP"
            ],
            "description": "ISO currency code."
          },
          "status": {
            "type": "string",
            "enum": [
              "awaiting_approval",
              "awaiting_final_approval",
              "returned",
              "approved",
              "paid",
              "voided"
            ],
            "description": "Where the invoice is in its lifecycle."
          },
          "overdue": {
            "type": "boolean",
            "description": "Derived: approved, unpaid, not voided and past dueDate."
          },
          "paymentStatus": {
            "type": "string",
            "nullable": true,
            "description": "A note set by payment processing, such as successful or refunded: NGN 1,000.00. Display it; don't branch on it."
          },
          "invoiceType": {
            "type": "string",
            "enum": [
              "setup",
              "build",
              "subscription",
              "usage",
              "performance",
              "mixed",
              "other",
              null
            ],
            "nullable": true,
            "description": "The kind of invoice."
          },
          "periodStart": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T00:00:00$",
            "description": "Start of the billing period.",
            "nullable": true
          },
          "periodEnd": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T00:00:00$",
            "description": "End of the billing period.",
            "nullable": true
          },
          "nextDueDate": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T00:00:00$",
            "description": "When the next cycle's invoice is due. Informational.",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?$",
            "description": "When the invoice was raised. Server time, no offset."
          },
          "approvedAt": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?$",
            "description": "When it was fully approved.",
            "nullable": true
          },
          "dueDate": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T00:00:00$",
            "description": "When payment is due."
          },
          "paidAt": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?$",
            "description": "When it was paid.",
            "nullable": true
          },
          "subtotal": {
            "type": "number",
            "format": "decimal",
            "description": "Sum of the lines."
          },
          "discount": {
            "type": "number",
            "format": "decimal",
            "description": "Discount on the subtotal."
          },
          "taxableNet": {
            "type": "number",
            "format": "decimal",
            "description": "Amount VAT is charged on."
          },
          "vatAmount": {
            "type": "number",
            "format": "decimal",
            "description": "VAT charged, from the organization's VAT setting."
          },
          "vatLabel": {
            "type": "string",
            "nullable": true,
            "description": "How the VAT line is labelled, such as VAT (7.5%). Comes from the organization's VAT setting."
          },
          "total": {
            "type": "number",
            "format": "decimal",
            "description": "Amount the customer pays."
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InvoiceLine"
            },
            "description": "The invoice lines."
          },
          "payments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Payment"
            },
            "nullable": true,
            "description": "Online payment attempts. Set only by GET /invoices/{id} when the key has payments:read; null everywhere else."
          }
        },
        "required": [
          "id",
          "number",
          "externalRef",
          "customerId",
          "currency",
          "status",
          "overdue",
          "paymentStatus",
          "invoiceType",
          "periodStart",
          "periodEnd",
          "nextDueDate",
          "createdAt",
          "approvedAt",
          "dueDate",
          "paidAt",
          "subtotal",
          "discount",
          "taxableNet",
          "vatAmount",
          "vatLabel",
          "total",
          "lines",
          "payments"
        ]
      },
      "InvoiceLine": {
        "type": "object",
        "description": "One line on an invoice.",
        "properties": {
          "serviceId": {
            "type": "integer",
            "format": "int32",
            "description": "The service in the organization's catalogue."
          },
          "description": {
            "type": "string",
            "description": "Ready to display: the service name, then — detail, then (qty × rate)."
          },
          "detail": {
            "type": "string",
            "nullable": true,
            "description": "Your detail text for the line."
          },
          "quantity": {
            "type": "number",
            "format": "decimal",
            "description": "Quantity. 1 when the line was sent as an amount."
          },
          "unitPrice": {
            "type": "number",
            "format": "decimal",
            "description": "Rate per unit."
          },
          "amount": {
            "type": "number",
            "format": "decimal",
            "description": "Line amount, net of VAT."
          }
        },
        "required": [
          "serviceId",
          "description",
          "detail",
          "quantity",
          "unitPrice",
          "amount"
        ]
      },
      "Payment": {
        "type": "object",
        "description": "An online payment attempt on an invoice.",
        "properties": {
          "reference": {
            "type": "string",
            "description": "The payment reference sent to Paystack."
          },
          "channel": {
            "type": "string",
            "description": "The payment channel, such as paystack."
          },
          "succeeded": {
            "type": "boolean",
            "description": "Whether Flux verified and accepted the payment."
          },
          "attemptedAt": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?$",
            "description": "When the attempt started. Server time, no offset."
          }
        },
        "required": [
          "reference",
          "channel",
          "succeeded",
          "attemptedAt"
        ]
      },
      "CreateInvoiceRequest": {
        "type": "object",
        "description": "Body for raising an invoice.",
        "properties": {
          "externalRef": {
            "type": "string",
            "description": "Your stable reference for this invoice, such as ws_42-2026-11. At most 100 characters, unique per organization. A repeat returns the existing invoice."
          },
          "customerId": {
            "type": "integer",
            "format": "int32",
            "description": "A customer in the organization."
          },
          "currency": {
            "type": "string",
            "enum": [
              "NGN",
              "USD",
              "CAD",
              "EUR",
              "GBP"
            ],
            "description": "A supported ISO currency code."
          },
          "dueDate": {
            "type": "string",
            "format": "date",
            "description": "When payment is due. Send YYYY-MM-DD."
          },
          "discount": {
            "type": "number",
            "format": "decimal",
            "description": "0 or more, and no more than the subtotal. Default 0."
          },
          "periodStart": {
            "type": "string",
            "format": "date",
            "description": "Billing period start. Send it together with periodEnd, or neither."
          },
          "periodEnd": {
            "type": "string",
            "format": "date",
            "description": "Billing period end. On or after periodStart."
          },
          "invoiceType": {
            "type": "string",
            "enum": [
              "setup",
              "build",
              "subscription",
              "usage",
              "performance",
              "mixed",
              "other"
            ],
            "description": "The kind of invoice."
          },
          "nextDueDate": {
            "type": "string",
            "format": "date",
            "description": "When the next cycle's invoice is due. Informational."
          },
          "comments": {
            "type": "string",
            "description": "A note for the organization's reviewers."
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InvoiceLineInput"
            },
            "description": "At least one line. The subtotal is the sum of the lines; you can't send a total."
          }
        },
        "required": [
          "customerId",
          "currency",
          "dueDate",
          "lines"
        ]
      },
      "InvoiceLineInput": {
        "type": "object",
        "description": "Send either amount, or quantity and unitPrice.",
        "properties": {
          "serviceId": {
            "type": "integer",
            "format": "int32",
            "description": "A service in the organization's catalogue. Lines may use different services."
          },
          "quantity": {
            "type": "number",
            "format": "decimal",
            "description": "Greater than 0. Flux multiplies it by unitPrice and rounds to 2 decimals."
          },
          "unitPrice": {
            "type": "number",
            "format": "decimal",
            "description": "0 or more."
          },
          "amount": {
            "type": "number",
            "format": "decimal",
            "description": "The line amount, when you don't send quantity and rate. Each line's amount must be greater than 0."
          },
          "detail": {
            "type": "string",
            "description": "Detail text shown after the service name. At most 500 characters."
          }
        },
        "required": [
          "serviceId"
        ]
      },
      "Receipt": {
        "type": "object",
        "description": "The receipt for a paid invoice.",
        "properties": {
          "invoiceId": {
            "type": "integer",
            "format": "int32",
            "description": "The paid invoice."
          },
          "invoiceNumber": {
            "type": "string",
            "description": "The invoice number."
          },
          "customerId": {
            "type": "integer",
            "format": "int32",
            "description": "The customer."
          },
          "currency": {
            "type": "string",
            "enum": [
              "NGN",
              "USD",
              "CAD",
              "EUR",
              "GBP"
            ],
            "description": "ISO currency code."
          },
          "amount": {
            "type": "number",
            "format": "decimal",
            "description": "The invoice total."
          },
          "paidAt": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?$",
            "description": "When it was paid. Server time, no offset.",
            "nullable": true
          },
          "paymentMethod": {
            "type": "string",
            "enum": [
              "online",
              "offline",
              null
            ],
            "nullable": true,
            "description": "online or offline."
          }
        },
        "required": [
          "invoiceId",
          "invoiceNumber",
          "customerId",
          "currency",
          "amount",
          "paidAt",
          "paymentMethod"
        ]
      },
      "Links": {
        "type": "object",
        "description": "Public, signed URLs you can show to end customers.",
        "properties": {
          "pdfUrl": {
            "type": "string",
            "description": "Opens the PDF. No API key needed."
          },
          "payUrl": {
            "type": "string",
            "nullable": true,
            "description": "Redirects to Paystack checkout. null when the invoice is paid or voided, and always on receipt links."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the links stop working. UTC, with Z."
          }
        },
        "required": [
          "pdfUrl",
          "payUrl",
          "expiresAt"
        ]
      },
      "Checkout": {
        "type": "object",
        "description": "Where to send the payer.",
        "properties": {
          "checkoutUrl": {
            "type": "string",
            "description": "The Paystack checkout page. Redirect the payer there."
          }
        },
        "required": [
          "checkoutUrl"
        ]
      },
      "Error": {
        "type": "object",
        "description": "Every v1 error has this body.",
        "properties": {
          "code": {
            "type": "string",
            "description": "Machine-readable. Branch on this."
          },
          "message": {
            "type": "string",
            "description": "Human-readable and safe to show to an operator."
          },
          "details": {
            "nullable": true,
            "description": "Extra data for some codes, else null."
          }
        },
        "required": [
          "code",
          "message",
          "details"
        ]
      },
      "WebhookEndpoint": {
        "type": "object",
        "description": "A URL that receives events.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32",
            "description": "Endpoint id."
          },
          "url": {
            "type": "string",
            "description": "Where events are sent."
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Your label."
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Event types it receives, or [\"*\"] for all."
          },
          "active": {
            "type": "boolean",
            "description": "Whether it receives events."
          },
          "disabledReason": {
            "type": "string",
            "nullable": true,
            "description": "Why Flux switched it off, if it did."
          },
          "consecutiveFailures": {
            "type": "integer",
            "format": "int32",
            "description": "Failed delivery attempts in a row. At 50, Flux switches the endpoint off."
          },
          "createdAt": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?$",
            "description": "When it was created. UTC, no offset."
          },
          "secret": {
            "type": "string",
            "nullable": true,
            "description": "The signing secret, whsec_…. Only on create and rotate, shown once; null otherwise."
          }
        },
        "required": [
          "id",
          "url",
          "description",
          "events",
          "active",
          "disabledReason",
          "consecutiveFailures",
          "createdAt",
          "secret"
        ]
      },
      "WebhookEndpointInput": {
        "type": "object",
        "description": "Body for registering or changing an endpoint. On update, every field is optional.",
        "properties": {
          "url": {
            "type": "string",
            "description": "An absolute https URL of at most 500 characters, publicly reachable. Required on create."
          },
          "description": {
            "type": "string",
            "description": "At most 200 characters."
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "At least one known event type, or [\"*\"] for all. Required on create."
          },
          "active": {
            "type": "boolean",
            "description": "Default true. Setting true re-enables a disabled endpoint and resets its failure count."
          }
        },
        "required": [
          "url",
          "events"
        ]
      },
      "Delivery": {
        "type": "object",
        "description": "One event sent to one endpoint, with its attempt history.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Delivery id. Use it to redeliver."
          },
          "eventId": {
            "type": "string",
            "description": "The event, evt_…. Same as the Flux-Event-Id header."
          },
          "eventType": {
            "type": "string",
            "description": "The event type."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "delivered",
              "dead",
              "cancelled"
            ],
            "description": "Delivery state."
          },
          "attempts": {
            "type": "integer",
            "format": "int32",
            "description": "Attempts so far. At most 10 before dead, plus any redeliveries."
          },
          "lastStatusCode": {
            "type": "integer",
            "format": "int32",
            "nullable": true,
            "description": "HTTP status of the last attempt."
          },
          "lastError": {
            "type": "string",
            "nullable": true,
            "description": "What went wrong on the last attempt, up to 500 characters."
          },
          "nextAttemptAt": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?$",
            "description": "When the next retry is due. UTC, no offset.",
            "nullable": true
          },
          "lastAttemptAt": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?$",
            "description": "UTC, no offset.",
            "nullable": true
          },
          "deliveredAt": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?$",
            "description": "When your endpoint returned 2xx. UTC, no offset.",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?$",
            "description": "When the delivery was queued. UTC, no offset."
          }
        },
        "required": [
          "id",
          "eventId",
          "eventType",
          "status",
          "attempts",
          "lastStatusCode",
          "lastError",
          "nextAttemptAt",
          "lastAttemptAt",
          "deliveredAt",
          "createdAt"
        ]
      },
      "EventEnvelope": {
        "type": "object",
        "description": "The body of every webhook delivery.",
        "properties": {
          "id": {
            "type": "string",
            "description": "evt_ followed by a ULID: unique and sortable by time. Deduplicate on it."
          },
          "type": {
            "type": "string",
            "description": "The event type, such as invoice.paid."
          },
          "created": {
            "type": "string",
            "format": "date-time",
            "description": "When the event happened. UTC, with Z."
          },
          "data": {
            "description": "{ \"object\": … }: the object for this event type."
          }
        },
        "required": [
          "id",
          "type",
          "created",
          "data"
        ]
      },
      "PaymentInitiated": {
        "type": "object",
        "description": "A checkout started.",
        "properties": {
          "reference": {
            "type": "string",
            "description": "Payment reference."
          },
          "invoiceId": {
            "type": "integer",
            "format": "int32",
            "description": "The invoice."
          },
          "invoiceNumber": {
            "type": "string",
            "description": "The invoice number."
          },
          "channel": {
            "type": "string",
            "description": "Always paystack."
          },
          "gateway": {
            "type": "string",
            "enum": [
              "platform",
              "custom",
              "legacy"
            ],
            "description": "Payment mode: platform (Flux's Paystack account), custom (the organization's own) or legacy (the organization hasn't set up payments)."
          },
          "platformFeeMinor": {
            "type": "integer",
            "format": "int64",
            "description": "Flux's platform fee added to the amount the payer pays, in minor units (kobo or cents). 0 when there's none."
          }
        },
        "required": [
          "reference",
          "invoiceId",
          "invoiceNumber",
          "channel",
          "gateway",
          "platformFeeMinor"
        ]
      },
      "PaymentOutcome": {
        "type": "object",
        "description": "An online payment was verified with Paystack.",
        "properties": {
          "reference": {
            "type": "string",
            "description": "Payment reference."
          },
          "invoiceId": {
            "type": "integer",
            "format": "int32",
            "description": "The invoice."
          },
          "invoiceNumber": {
            "type": "string",
            "description": "The invoice number."
          },
          "channel": {
            "type": "string",
            "description": "The payment channel."
          },
          "succeeded": {
            "type": "boolean",
            "description": "true on payment.succeeded, false on payment.failed."
          },
          "problem": {
            "type": "string",
            "nullable": true,
            "description": "Why the money didn't satisfy the invoice, as text, such as paid NGN 858,500.00, invoice requires NGN 860,000.00. null on success, and when Paystack itself declined the payment. Display it; don't parse it."
          }
        },
        "required": [
          "reference",
          "invoiceId",
          "invoiceNumber",
          "channel",
          "succeeded",
          "problem"
        ]
      },
      "PaymentRefunded": {
        "type": "object",
        "description": "A refund was made in Paystack.",
        "properties": {
          "reference": {
            "type": "string",
            "description": "Payment reference."
          },
          "invoiceId": {
            "type": "integer",
            "format": "int32",
            "description": "The invoice."
          },
          "invoiceNumber": {
            "type": "string",
            "description": "The invoice number."
          },
          "amountMinor": {
            "type": "integer",
            "format": "int64",
            "description": "Amount refunded in minor units (kobo or cents)."
          },
          "currency": {
            "type": "string",
            "description": "ISO currency code."
          }
        },
        "required": [
          "reference",
          "invoiceId",
          "invoiceNumber",
          "amountMinor",
          "currency"
        ]
      },
      "ReceiptEvent": {
        "type": "object",
        "description": "A receipt is available. Same as the receipt object, without paymentMethod.",
        "properties": {
          "invoiceId": {
            "type": "integer",
            "format": "int32",
            "description": "The paid invoice."
          },
          "invoiceNumber": {
            "type": "string",
            "description": "The invoice number."
          },
          "customerId": {
            "type": "integer",
            "format": "int32",
            "description": "The customer."
          },
          "currency": {
            "type": "string",
            "description": "ISO currency code."
          },
          "amount": {
            "type": "number",
            "format": "decimal",
            "description": "The invoice total."
          },
          "paidAt": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?$",
            "description": "When it was paid. Server time, no offset.",
            "nullable": true
          }
        },
        "required": [
          "invoiceId",
          "invoiceNumber",
          "customerId",
          "currency",
          "amount",
          "paidAt"
        ]
      },
      "EnrollmentEvent": {
        "type": "object",
        "description": "Sent with enrollment.* events. Unlike the enrollments endpoint, it includes the billing cycle.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32",
            "description": "Enrollment id."
          },
          "customerId": {
            "type": "integer",
            "format": "int32",
            "description": "The customer."
          },
          "serviceId": {
            "type": "integer",
            "format": "int32",
            "description": "The service."
          },
          "amount": {
            "type": "number",
            "format": "decimal",
            "description": "The agreed amount, else the service's price."
          },
          "billingCycle": {
            "type": "string",
            "enum": [
              "monthly",
              "quarterly",
              "half_yearly",
              "yearly",
              null
            ],
            "nullable": true,
            "description": "How often the service is billed."
          },
          "nextBillingDate": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T00:00:00$",
            "description": "The next billing date.",
            "nullable": true
          },
          "cycleDate": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T00:00:00$",
            "description": "The billing date that arrived. Set only on enrollment.cycle_due.",
            "nullable": true
          },
          "enrolledFrom": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T00:00:00$",
            "description": "Start date."
          },
          "enrolledTo": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T00:00:00$",
            "description": "End date.",
            "nullable": true
          },
          "active": {
            "type": "boolean",
            "description": "Whether the enrollment is active."
          }
        },
        "required": [
          "id",
          "customerId",
          "serviceId",
          "amount",
          "billingCycle",
          "nextBillingDate",
          "cycleDate",
          "enrolledFrom",
          "enrolledTo",
          "active"
        ]
      },
      "WebhookTest": {
        "type": "object",
        "description": "Sent by the test endpoint and the Test button in the Flux app.",
        "properties": {
          "endpointId": {
            "type": "integer",
            "format": "int32",
            "description": "The endpoint tested."
          },
          "message": {
            "type": "string",
            "description": "Always Test event from Flux ERP."
          }
        },
        "required": [
          "endpointId",
          "message"
        ]
      },
      "Page": {
        "type": "object",
        "required": [
          "data",
          "nextCursor",
          "hasMore"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {}
          },
          "nextCursor": {
            "type": "string",
            "nullable": true
          },
          "hasMore": {
            "type": "boolean"
          }
        }
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Makes a retry safe for 24 hours. At most 255 characters.",
        "schema": {
          "type": "string",
          "maxLength": 255
        }
      }
    }
  }
}
