{
  "openapi": "3.1.0",
  "info": {
    "title": "CentraPoint API",
    "version": "1.8.1",
    "summary": "Create hosted payment links and look up payment status.",
    "description": "The CentraPoint REST API lets your systems manage customers and invoices, create hosted payment links, subscriptions and EFT orders, check the status of payments and read reconciliation results. Every API key may make 120 requests per minute; responses report usage in `X-RateLimit-*` headers. All requests are authenticated with a tenant-scoped API key sent as a Bearer token. API access requires a plan that includes the REST API (Growth or higher).\n\nOutbound webhooks that CentraPoint sends to your endpoints are described in the `webhooks` section. Gateway notification URLs (`/api/webhooks/{type}`) are called by payment gateways, not by API clients, and are therefore not part of this specification.",
    "contact": {
      "name": "CentraPoint",
      "url": "https://centrapoint.co.za"
    }
  },
  "servers": [
    {
      "url": "https://app.centrapoint.co.za",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Payment links",
      "description": "Hosted checkout links you can send to payers."
    },
    {
      "name": "Customers",
      "description": "Your customers, keyed by your own externalReference."
    },
    {
      "name": "Invoices",
      "description": "Invoices with VAT, pay links and offline payments."
    },
    {
      "name": "Subscriptions",
      "description": "Gateway-managed card subscriptions started by recurring payment links."
    },
    {
      "name": "EFT orders",
      "description": "Bank transfers with a unique deposit reference and proof of payment."
    },
    {
      "name": "Transactions",
      "description": "Payment attempts created when a payer checks out."
    },
    {
      "name": "Reconciliation",
      "description": "Results of statement reconciliation runs."
    },
    {
      "name": "Accounting",
      "description": "Accounting-system partner links for your own apps."
    }
  ],
  "paths": {
    "/api/v1/payment-links": {
      "post": {
        "tags": [
          "Payment links"
        ],
        "operationId": "createPaymentLink",
        "summary": "Create a payment link",
        "description": "Creates a hosted payment link at `/pay/{token}`. When `customerEmail` is supplied, the link is attached to the existing customer with that email address in your organisation, or a new customer is created (subject to your plan's customer limit).\n\nRequires the API and Payment links features on your plan, and an account that is not read-only (suspended, cancelled or expired trial).\n\nPass `externalReference` (your order ID) so you can later find the link and the payment references made through it with `GET /api/v1/payment-links?externalReference=`.\n\nUnknown body fields are ignored. This endpoint is **not idempotent**: retrying a request creates another link.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePaymentLinkRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "Minimal",
                  "value": {
                    "title": "Website hosting - October",
                    "amount": 499
                  }
                },
                "full": {
                  "summary": "With customer and expiry",
                  "value": {
                    "title": "Website hosting - October",
                    "description": "Monthly hosting for example.co.za",
                    "amount": 499,
                    "currency": "ZAR",
                    "externalReference": "ORDER-1001",
                    "customerEmail": "client@example.com",
                    "customerFirstName": "Thandi",
                    "customerLastName": "Nkosi",
                    "singleUse": true,
                    "expiresAt": "2026-10-31T23:59:59+02:00",
                    "returnUrl": "https://shop.example.co.za/checkout/done",
                    "metadata": {
                      "crmId": "0061x00000AbCdE"
                    }
                  }
                },
                "recurring": {
                  "summary": "Monthly subscription",
                  "value": {
                    "title": "Pro plan",
                    "amount": 499,
                    "externalReference": "SUB-ACME-PRO",
                    "customerId": "cmg2c0s7t0003cust0001abcd",
                    "recurring": {
                      "frequency": "monthly"
                    },
                    "singleUse": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payment link created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentLink"
                },
                "example": {
                  "id": "cmg1k2x3y0001abcd1234efgh",
                  "url": "https://app.centrapoint.co.za/pay/Xb7kP2qR9sLm4TvWz8Yc",
                  "title": "Website hosting - October",
                  "description": "Monthly hosting for example.co.za",
                  "amount": 499,
                  "currency": "ZAR",
                  "externalReference": "ORDER-1001",
                  "singleUse": true,
                  "active": true,
                  "expiresAt": "2026-10-31T21:59:59.000Z",
                  "paidCount": 0,
                  "createdAt": "2026-09-25T08:10:02.114Z",
                  "returnUrl": "https://shop.example.co.za/checkout/done",
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "recurring": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            }
          },
          "400": {
            "description": "The body failed validation (`issues` lists each problem), or a business rule failed: unknown product/invoice, invalid or unsupported currency, or `expiresAt` not in the future / more than 1 year ahead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "validation": {
                    "summary": "Validation failure",
                    "value": {
                      "error": "invalid_request",
                      "message": "Invalid body",
                      "issues": [
                        {
                          "origin": "number",
                          "code": "too_small",
                          "minimum": 0,
                          "inclusive": false,
                          "path": [
                            "amount"
                          ],
                          "message": "Too small: expected number to be >0"
                        }
                      ]
                    }
                  },
                  "notFound": {
                    "summary": "Unknown product",
                    "value": {
                      "error": "invalid_request",
                      "message": "Product not found"
                    }
                  },
                  "currency": {
                    "summary": "Currency not accepted",
                    "value": {
                      "error": "invalid_request",
                      "message": "None of your enabled payment gateways accept USD. Enable a gateway that supports USD first."
                    }
                  },
                  "expiry": {
                    "summary": "Expiry in the past",
                    "value": {
                      "error": "invalid_request",
                      "message": "expiresAt must be in the future"
                    }
                  },
                  "returnUrl": {
                    "summary": "returnUrl origin not allowed",
                    "value": {
                      "error": "invalid_request",
                      "message": "returnUrl origin https://shop.example.co.za is not allowed - add it under Settings > API keys > Allowed return URLs"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Your plan does not include the API or payment links (`plan_restricted`), a plan quota such as the customer limit was reached (`plan_limit`), or the account is read-only (`account_restricted`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "planRestricted": {
                    "value": {
                      "error": "plan_restricted",
                      "message": "This feature is not included in your plan"
                    }
                  },
                  "planLimit": {
                    "value": {
                      "error": "plan_limit",
                      "message": "Customers limit reached (2000/2000) on the Growth plan. Upgrade under Settings > Billing."
                    }
                  },
                  "accountRestricted": {
                    "value": {
                      "error": "account_restricted",
                      "message": "Your free trial has ended. Choose a plan to continue."
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "409": {
            "description": "The Idempotency-Key was already used with a different request body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "idempotency_conflict",
                  "message": "This Idempotency-Key was already used with a different request body"
                }
              }
            }
          },
          "413": {
            "description": "Request body larger than 100 000 characters.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "message": "Request body too large"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      },
      "get": {
        "tags": [
          "Payment links"
        ],
        "operationId": "listPaymentLinks",
        "summary": "List payment links",
        "description": "Returns your organisation's payment links, newest first, each with the payments made through it (up to 50 per link). Filter by `externalReference` to find the link for one of your orders and discover its payment references.",
        "parameters": [
          {
            "name": "externalReference",
            "in": "query",
            "required": false,
            "description": "Only links with exactly this external reference (leading/trailing whitespace ignored).",
            "schema": {
              "type": "string"
            },
            "example": "ORDER-1001"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of links to return, 1–100. Invalid or missing values fall back to 20; values out of range are clamped.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "example": 20
          }
        ],
        "responses": {
          "200": {
            "description": "Matching links.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentLinkList"
                },
                "example": {
                  "data": [
                    {
                      "id": "cmg1k2x3y0001abcd1234efgh",
                      "url": "https://app.centrapoint.co.za/pay/Xb7kP2qR9sLm4TvWz8Yc",
                      "title": "Website hosting - October",
                      "description": "Monthly hosting for example.co.za",
                      "amount": 499,
                      "currency": "ZAR",
                      "externalReference": "ORDER-1001",
                      "singleUse": true,
                      "active": false,
                      "expiresAt": "2026-10-31T21:59:59.000Z",
                      "paidCount": 1,
                      "createdAt": "2026-09-25T08:10:02.114Z",
                      "payments": [
                        {
                          "reference": "CP-20260925-9F3A1C7B",
                          "status": "complete",
                          "amount": 499,
                          "currency": "ZAR",
                          "paidAt": "2026-09-25T08:14:03.512Z",
                          "createdAt": "2026-09-25T08:12:47.020Z"
                        },
                        {
                          "reference": "CP-20260925-41D07E2A",
                          "status": "cancelled",
                          "amount": 499,
                          "currency": "ZAR",
                          "paidAt": null,
                          "createdAt": "2026-09-25T08:11:15.873Z"
                        }
                      ],
                      "returnUrl": null,
                      "metadata": {},
                      "recurring": null,
                      "subscription": null
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "API access is not included in your plan.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "API access is not included in your plan"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/payment-links/{id}": {
      "get": {
        "tags": [
          "Payment links"
        ],
        "operationId": "getPaymentLink",
        "summary": "Get a payment link",
        "description": "Returns one payment link in your organisation with the payments made through it (newest first, up to 100).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Payment link ID returned when the link was created.",
            "schema": {
              "type": "string"
            },
            "example": "cmg1k2x3y0001abcd1234efgh"
          }
        ],
        "responses": {
          "200": {
            "description": "The payment link.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentLinkWithPayments"
                },
                "example": {
                  "id": "cmg1k2x3y0001abcd1234efgh",
                  "url": "https://app.centrapoint.co.za/pay/Xb7kP2qR9sLm4TvWz8Yc",
                  "title": "Website hosting - October",
                  "description": "Monthly hosting for example.co.za",
                  "amount": 499,
                  "currency": "ZAR",
                  "externalReference": "ORDER-1001",
                  "singleUse": true,
                  "active": false,
                  "expiresAt": "2026-10-31T21:59:59.000Z",
                  "paidCount": 1,
                  "createdAt": "2026-09-25T08:10:02.114Z",
                  "payments": [
                    {
                      "reference": "CP-20260925-9F3A1C7B",
                      "status": "complete",
                      "amount": 499,
                      "currency": "ZAR",
                      "paidAt": "2026-09-25T08:14:03.512Z",
                      "createdAt": "2026-09-25T08:12:47.020Z"
                    },
                    {
                      "reference": "CP-20260925-41D07E2A",
                      "status": "cancelled",
                      "amount": 499,
                      "currency": "ZAR",
                      "paidAt": null,
                      "createdAt": "2026-09-25T08:11:15.873Z"
                    }
                  ],
                  "returnUrl": null,
                  "metadata": {},
                  "recurring": null,
                  "subscription": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "API access is not included in your plan.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "API access is not included in your plan"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "404": {
            "description": "No payment link with this ID in your organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "Payment link not found"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/customers": {
      "post": {
        "tags": [
          "Customers"
        ],
        "operationId": "createCustomer",
        "summary": "Create or upsert a customer",
        "description": "Without `externalReference`: creates a customer (201). With `externalReference`: upsert keyed on it — updates the customer with that reference (200), otherwise adopts an existing customer with the same email and no reference (200), otherwise creates one (201). Available on any plan with API access; creating counts towards your customer limit.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerCreate"
              },
              "example": {
                "externalReference": "CRM-1001",
                "email": "thandi@example.co.za",
                "firstName": "Thandi",
                "lastName": "Nkosi",
                "company": "Nkosi Trading (Pty) Ltd",
                "vatNumber": "4123456789",
                "phone": "+27821234567",
                "metadata": {
                  "crmId": "0061x00000AbCdE"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing customer updated (upsert).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "example": {
                  "id": "cmg2c0s7t0003cust0001abcd",
                  "accountCode": "CUS-00042",
                  "externalReference": "CRM-1001",
                  "email": "thandi@example.co.za",
                  "firstName": "Thandi",
                  "lastName": "Nkosi",
                  "company": "Nkosi Trading (Pty) Ltd",
                  "vatNumber": "4123456789",
                  "phone": "+27821234567",
                  "address": "12 Long Street, Cape Town, 8001",
                  "active": true,
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "createdAt": "2026-09-25T08:00:00.000Z",
                  "updatedAt": "2026-09-25T08:00:00.000Z"
                }
              }
            }
          },
          "201": {
            "description": "Customer created.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "example": {
                  "id": "cmg2c0s7t0003cust0001abcd",
                  "accountCode": "CUS-00042",
                  "externalReference": "CRM-1001",
                  "email": "thandi@example.co.za",
                  "firstName": "Thandi",
                  "lastName": "Nkosi",
                  "company": "Nkosi Trading (Pty) Ltd",
                  "vatNumber": "4123456789",
                  "phone": "+27821234567",
                  "address": "12 Long Street, Cape Town, 8001",
                  "active": true,
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "createdAt": "2026-09-25T08:00:00.000Z",
                  "updatedAt": "2026-09-25T08:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or invalid metadata.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "message": "Invalid body",
                  "issues": [
                    {
                      "code": "invalid_format",
                      "format": "email",
                      "path": [
                        "email"
                      ],
                      "message": "Invalid email address"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not include the API, customer limit reached (`plan_limit`), or account read-only.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_limit",
                  "message": "Customers limit reached (2000/2000) on the Growth plan. Upgrade under Settings > Billing."
                }
              }
            }
          },
          "409": {
            "description": "The Idempotency-Key was already used with a different request body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "idempotency_conflict",
                  "message": "This Idempotency-Key was already used with a different request body"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Customers"
        ],
        "operationId": "listCustomers",
        "summary": "List customers",
        "description": "Newest first.",
        "parameters": [
          {
            "name": "externalReference",
            "in": "query",
            "description": "Exact match on your customer ID.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "description": "Exact match (case-insensitive).",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Customers.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerList"
                },
                "example": {
                  "data": [
                    {
                      "id": "cmg2c0s7t0003cust0001abcd",
                      "accountCode": "CUS-00042",
                      "externalReference": "CRM-1001",
                      "email": "thandi@example.co.za",
                      "firstName": "Thandi",
                      "lastName": "Nkosi",
                      "company": "Nkosi Trading (Pty) Ltd",
                      "vatNumber": "4123456789",
                      "phone": "+27821234567",
                      "address": "12 Long Street, Cape Town, 8001",
                      "active": true,
                      "metadata": {
                        "crmId": "0061x00000AbCdE"
                      },
                      "createdAt": "2026-09-25T08:00:00.000Z",
                      "updatedAt": "2026-09-25T08:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "API access (or the feature) is not included in your plan.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/customers/{id}": {
      "get": {
        "tags": [
          "Customers"
        ],
        "operationId": "getCustomer",
        "summary": "Get a customer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "cmg2c0s7t0003cust0001abcd"
          }
        ],
        "responses": {
          "200": {
            "description": "The customer.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "example": {
                  "id": "cmg2c0s7t0003cust0001abcd",
                  "accountCode": "CUS-00042",
                  "externalReference": "CRM-1001",
                  "email": "thandi@example.co.za",
                  "firstName": "Thandi",
                  "lastName": "Nkosi",
                  "company": "Nkosi Trading (Pty) Ltd",
                  "vatNumber": "4123456789",
                  "phone": "+27821234567",
                  "address": "12 Long Street, Cape Town, 8001",
                  "active": true,
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "createdAt": "2026-09-25T08:00:00.000Z",
                  "updatedAt": "2026-09-25T08:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "API access (or the feature) is not included in your plan.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "404": {
            "description": "No customer with this ID in your organisation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "Customer not found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Customers"
        ],
        "operationId": "updateCustomer",
        "summary": "Update a customer",
        "description": "Partial update. Omitted fields are unchanged; `null` clears optional fields; `metadata` replaces the whole map. Unknown fields are rejected. Idempotency-Key is not used (PATCH is naturally repeatable).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "cmg2c0s7t0003cust0001abcd"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerUpdate"
              },
              "example": {
                "phone": "+27829876543",
                "company": null,
                "active": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated customer.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "example": {
                  "id": "cmg2c0s7t0003cust0001abcd",
                  "accountCode": "CUS-00042",
                  "externalReference": "CRM-1001",
                  "email": "thandi@example.co.za",
                  "firstName": "Thandi",
                  "lastName": "Nkosi",
                  "company": "Nkosi Trading (Pty) Ltd",
                  "vatNumber": "4123456789",
                  "phone": "+27821234567",
                  "address": "12 Long Street, Cape Town, 8001",
                  "active": true,
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "createdAt": "2026-09-25T08:00:00.000Z",
                  "updatedAt": "2026-09-25T08:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed, unknown field, or externalReference used by another customer.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "message": "Another customer already has that externalReference"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Account is read-only (suspended, cancelled or trial ended), or a plan feature/quota is missing.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "account_restricted",
                  "message": "Your free trial has ended. Choose a plan to continue."
                }
              }
            }
          },
          "404": {
            "description": "No customer with this ID in your organisation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "Customer not found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/invoices": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "operationId": "createInvoice",
        "summary": "Create an invoice",
        "description": "Creates an invoice with per-line VAT (201). If `externalReference` matches an existing invoice, that invoice is returned unchanged with 200 (the body is not applied and it is not re-sent). A pay link for the balance is created when your plan includes payment links and a gateway accepts the currency. With `send: true` the invoice is created as `sent` and emailed. Requires the Invoicing feature.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceCreate"
              },
              "example": {
                "customer": {
                  "externalReference": "CRM-1001"
                },
                "externalReference": "ORDER-1001",
                "dueDate": "2026-10-08",
                "lines": [
                  {
                    "description": "Website hosting - October",
                    "quantity": 1,
                    "unitPrice": 434.78
                  },
                  {
                    "description": "Domain renewal (zero-rated)",
                    "quantity": 1,
                    "unitPrice": 120,
                    "taxRate": 0
                  }
                ],
                "metadata": {
                  "crmId": "0061x00000AbCdE"
                },
                "send": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An invoice with this externalReference already existed; returned unchanged.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                },
                "example": {
                  "id": "cmg2i9n0v0005inv0001abcd",
                  "number": "INV-000042",
                  "status": "sent",
                  "externalReference": "ORDER-1001",
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001",
                    "email": "thandi@example.co.za"
                  },
                  "currency": "ZAR",
                  "issueDate": "2026-09-24T22:00:00.000Z",
                  "dueDate": "2026-10-08T21:59:59.000Z",
                  "lines": [
                    {
                      "description": "Website hosting - October",
                      "quantity": 1,
                      "unitPrice": 434.78,
                      "taxRate": 15,
                      "amount": 434.78
                    },
                    {
                      "description": "Domain renewal (zero-rated)",
                      "quantity": 1,
                      "unitPrice": 120,
                      "taxRate": 0,
                      "amount": 120
                    }
                  ],
                  "subtotal": 554.78,
                  "taxAmount": 65.22,
                  "total": 620,
                  "amountPaid": 0,
                  "amountDue": 620,
                  "paidAt": null,
                  "payUrl": "https://app.centrapoint.co.za/pay/Xb7kP2qR9sLm4TvWz8Yc",
                  "notes": "Payment due within 14 days.",
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "createdAt": "2026-09-25T08:05:00.000Z",
                  "updatedAt": "2026-09-25T08:05:00.000Z"
                }
              }
            }
          },
          "201": {
            "description": "Invoice created.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                },
                "example": {
                  "id": "cmg2i9n0v0005inv0001abcd",
                  "number": "INV-000042",
                  "status": "sent",
                  "externalReference": "ORDER-1001",
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001",
                    "email": "thandi@example.co.za"
                  },
                  "currency": "ZAR",
                  "issueDate": "2026-09-24T22:00:00.000Z",
                  "dueDate": "2026-10-08T21:59:59.000Z",
                  "lines": [
                    {
                      "description": "Website hosting - October",
                      "quantity": 1,
                      "unitPrice": 434.78,
                      "taxRate": 15,
                      "amount": 434.78
                    },
                    {
                      "description": "Domain renewal (zero-rated)",
                      "quantity": 1,
                      "unitPrice": 120,
                      "taxRate": 0,
                      "amount": 120
                    }
                  ],
                  "subtotal": 554.78,
                  "taxAmount": 65.22,
                  "total": 620,
                  "amountPaid": 0,
                  "amountDue": 620,
                  "paidAt": null,
                  "payUrl": "https://app.centrapoint.co.za/pay/Xb7kP2qR9sLm4TvWz8Yc",
                  "notes": "Payment due within 14 days.",
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "createdAt": "2026-09-25T08:05:00.000Z",
                  "updatedAt": "2026-09-25T08:05:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed, invalid ISO 4217 currency, customer not found, or totals invalid.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "message": "Customer not found"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not include the API or invoicing, or account read-only.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "409": {
            "description": "The Idempotency-Key was already used with a different request body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "idempotency_conflict",
                  "message": "This Idempotency-Key was already used with a different request body"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Invoices"
        ],
        "operationId": "listInvoices",
        "summary": "List invoices",
        "description": "Newest first. `status=draft|sent|overdue` returns only invoices with nothing paid; use `partially_paid` for those with part payments.",
        "parameters": [
          {
            "name": "customerId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "externalReference",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "sent",
                "overdue",
                "partially_paid",
                "paid",
                "cancelled"
              ],
              "description": "`partially_paid` is reported for draft/sent/overdue invoices that have received some money."
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Invoices.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceList"
                },
                "example": {
                  "data": [
                    {
                      "id": "cmg2i9n0v0005inv0001abcd",
                      "number": "INV-000042",
                      "status": "sent",
                      "externalReference": "ORDER-1001",
                      "customer": {
                        "id": "cmg2c0s7t0003cust0001abcd",
                        "externalReference": "CRM-1001",
                        "email": "thandi@example.co.za"
                      },
                      "currency": "ZAR",
                      "issueDate": "2026-09-24T22:00:00.000Z",
                      "dueDate": "2026-10-08T21:59:59.000Z",
                      "lines": [
                        {
                          "description": "Website hosting - October",
                          "quantity": 1,
                          "unitPrice": 434.78,
                          "taxRate": 15,
                          "amount": 434.78
                        },
                        {
                          "description": "Domain renewal (zero-rated)",
                          "quantity": 1,
                          "unitPrice": 120,
                          "taxRate": 0,
                          "amount": 120
                        }
                      ],
                      "subtotal": 554.78,
                      "taxAmount": 65.22,
                      "total": 620,
                      "amountPaid": 0,
                      "amountDue": 620,
                      "paidAt": null,
                      "payUrl": "https://app.centrapoint.co.za/pay/Xb7kP2qR9sLm4TvWz8Yc",
                      "notes": "Payment due within 14 days.",
                      "metadata": {
                        "crmId": "0061x00000AbCdE"
                      },
                      "createdAt": "2026-09-25T08:05:00.000Z",
                      "updatedAt": "2026-09-25T08:05:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Unknown status.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "message": "status must be one of draft, sent, overdue, paid, cancelled, partially_paid"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not include the API or invoicing, or account read-only.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/invoices/{id}": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "operationId": "getInvoice",
        "summary": "Get an invoice",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "cmg2i9n0v0005inv0001abcd"
          }
        ],
        "responses": {
          "200": {
            "description": "The invoice.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                },
                "example": {
                  "id": "cmg2i9n0v0005inv0001abcd",
                  "number": "INV-000042",
                  "status": "sent",
                  "externalReference": "ORDER-1001",
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001",
                    "email": "thandi@example.co.za"
                  },
                  "currency": "ZAR",
                  "issueDate": "2026-09-24T22:00:00.000Z",
                  "dueDate": "2026-10-08T21:59:59.000Z",
                  "lines": [
                    {
                      "description": "Website hosting - October",
                      "quantity": 1,
                      "unitPrice": 434.78,
                      "taxRate": 15,
                      "amount": 434.78
                    },
                    {
                      "description": "Domain renewal (zero-rated)",
                      "quantity": 1,
                      "unitPrice": 120,
                      "taxRate": 0,
                      "amount": 120
                    }
                  ],
                  "subtotal": 554.78,
                  "taxAmount": 65.22,
                  "total": 620,
                  "amountPaid": 0,
                  "amountDue": 620,
                  "paidAt": null,
                  "payUrl": "https://app.centrapoint.co.za/pay/Xb7kP2qR9sLm4TvWz8Yc",
                  "notes": "Payment due within 14 days.",
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "createdAt": "2026-09-25T08:05:00.000Z",
                  "updatedAt": "2026-09-25T08:05:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not include the API or invoicing, or account read-only.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "404": {
            "description": "No invoice with this ID in your organisation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "Invoice not found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/invoices/{id}/send": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "operationId": "sendInvoice",
        "summary": "Send an invoice",
        "description": "Emails the invoice to the customer, with a pay link for the balance when possible. A draft becomes `sent`. Paid or cancelled invoices cannot be sent. No body is required.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "cmg2i9n0v0005inv0001abcd"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The invoice after sending.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                },
                "example": {
                  "id": "cmg2i9n0v0005inv0001abcd",
                  "number": "INV-000042",
                  "status": "sent",
                  "externalReference": "ORDER-1001",
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001",
                    "email": "thandi@example.co.za"
                  },
                  "currency": "ZAR",
                  "issueDate": "2026-09-24T22:00:00.000Z",
                  "dueDate": "2026-10-08T21:59:59.000Z",
                  "lines": [
                    {
                      "description": "Website hosting - October",
                      "quantity": 1,
                      "unitPrice": 434.78,
                      "taxRate": 15,
                      "amount": 434.78
                    },
                    {
                      "description": "Domain renewal (zero-rated)",
                      "quantity": 1,
                      "unitPrice": 120,
                      "taxRate": 0,
                      "amount": 120
                    }
                  ],
                  "subtotal": 554.78,
                  "taxAmount": 65.22,
                  "total": 620,
                  "amountPaid": 0,
                  "amountDue": 620,
                  "paidAt": null,
                  "payUrl": "https://app.centrapoint.co.za/pay/Xb7kP2qR9sLm4TvWz8Yc",
                  "notes": "Payment due within 14 days.",
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "createdAt": "2026-09-25T08:05:00.000Z",
                  "updatedAt": "2026-09-25T08:05:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "The invoice is in a state that does not allow this, or validation failed.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "message": "Invoice is paid"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not include the API or invoicing, or account read-only.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "404": {
            "description": "No invoice with this ID in your organisation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "Invoice not found"
                }
              }
            }
          },
          "409": {
            "description": "The Idempotency-Key was already used with a different request body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "idempotency_conflict",
                  "message": "This Idempotency-Key was already used with a different request body"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/invoices/{id}/cancel": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "operationId": "cancelInvoice",
        "summary": "Cancel an invoice",
        "description": "Voids an unpaid (or partially paid) invoice and deactivates its pay links. Cancelling an already cancelled invoice returns it unchanged. Paid invoices cannot be cancelled.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "cmg2i9n0v0005inv0001abcd"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceCancel"
              },
              "example": {
                "reason": "Order cancelled by customer"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cancelled invoice.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                },
                "example": {
                  "id": "cmg2i9n0v0005inv0001abcd",
                  "number": "INV-000042",
                  "status": "cancelled",
                  "externalReference": "ORDER-1001",
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001",
                    "email": "thandi@example.co.za"
                  },
                  "currency": "ZAR",
                  "issueDate": "2026-09-24T22:00:00.000Z",
                  "dueDate": "2026-10-08T21:59:59.000Z",
                  "lines": [
                    {
                      "description": "Website hosting - October",
                      "quantity": 1,
                      "unitPrice": 434.78,
                      "taxRate": 15,
                      "amount": 434.78
                    },
                    {
                      "description": "Domain renewal (zero-rated)",
                      "quantity": 1,
                      "unitPrice": 120,
                      "taxRate": 0,
                      "amount": 120
                    }
                  ],
                  "subtotal": 554.78,
                  "taxAmount": 65.22,
                  "total": 620,
                  "amountPaid": 0,
                  "amountDue": 620,
                  "paidAt": null,
                  "payUrl": null,
                  "notes": "Payment due within 14 days.",
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "createdAt": "2026-09-25T08:05:00.000Z",
                  "updatedAt": "2026-09-25T08:05:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "The invoice is in a state that does not allow this, or validation failed.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "message": "Paid invoices cannot be cancelled - record a refund or credit instead"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not include the API or invoicing, or account read-only.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "404": {
            "description": "No invoice with this ID in your organisation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "Invoice not found"
                }
              }
            }
          },
          "409": {
            "description": "The Idempotency-Key was already used with a different request body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "idempotency_conflict",
                  "message": "This Idempotency-Key was already used with a different request body"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/invoices/{id}/payments": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "operationId": "recordInvoicePayment",
        "summary": "Record an offline payment",
        "description": "Records money received outside CentraPoint's gateways (EFT, cash, card machine, wallet credit…). The invoice becomes `paid` when fully paid (pay links are deactivated) or `partially_paid` otherwise. Always send an Idempotency-Key so a retry does not record the payment twice.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "cmg2i9n0v0005inv0001abcd"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoicePaymentCreate"
              },
              "example": {
                "amount": 620,
                "method": "eft",
                "reference": "FNB 2026-10-02 NKOSI",
                "paidAt": "2026-10-02T10:15:00+02:00"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payment recorded; the updated invoice.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                },
                "example": {
                  "id": "cmg2i9n0v0005inv0001abcd",
                  "number": "INV-000042",
                  "status": "paid",
                  "externalReference": "ORDER-1001",
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001",
                    "email": "thandi@example.co.za"
                  },
                  "currency": "ZAR",
                  "issueDate": "2026-09-24T22:00:00.000Z",
                  "dueDate": "2026-10-08T21:59:59.000Z",
                  "lines": [
                    {
                      "description": "Website hosting - October",
                      "quantity": 1,
                      "unitPrice": 434.78,
                      "taxRate": 15,
                      "amount": 434.78
                    },
                    {
                      "description": "Domain renewal (zero-rated)",
                      "quantity": 1,
                      "unitPrice": 120,
                      "taxRate": 0,
                      "amount": 120
                    }
                  ],
                  "subtotal": 554.78,
                  "taxAmount": 65.22,
                  "total": 620,
                  "amountPaid": 620,
                  "amountDue": 0,
                  "paidAt": "2026-10-02T08:15:00.000Z",
                  "payUrl": null,
                  "notes": "Payment due within 14 days.",
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "createdAt": "2026-09-25T08:05:00.000Z",
                  "updatedAt": "2026-09-25T08:05:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "The invoice is in a state that does not allow this, or validation failed.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "message": "Amount exceeds the outstanding balance of 620.00"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not include the API or invoicing, or account read-only.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "404": {
            "description": "No invoice with this ID in your organisation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "Invoice not found"
                }
              }
            }
          },
          "409": {
            "description": "The Idempotency-Key was already used with a different request body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "idempotency_conflict",
                  "message": "This Idempotency-Key was already used with a different request body"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/subscriptions": {
      "get": {
        "tags": [
          "Subscriptions"
        ],
        "operationId": "listSubscriptions",
        "summary": "List subscriptions",
        "description": "Gateway-managed subscriptions started by recurring payment links, newest first.",
        "parameters": [
          {
            "name": "externalReference",
            "in": "query",
            "description": "The payment link's externalReference.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Subscriptions.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionList"
                },
                "example": {
                  "data": [
                    {
                      "id": "cmg2s5b8k0007sub0001abcd",
                      "status": "active",
                      "frequency": "monthly",
                      "amount": 499,
                      "currency": "ZAR",
                      "gateway": "payfast",
                      "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                      "externalReference": "SUB-ACME-PRO",
                      "metadata": {
                        "plan": "pro"
                      },
                      "createdAt": "2026-09-25T08:14:04.000Z",
                      "updatedAt": "2026-09-25T08:14:04.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "API access (or the feature) is not included in your plan.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/subscriptions/{id}": {
      "get": {
        "tags": [
          "Subscriptions"
        ],
        "operationId": "getSubscription",
        "summary": "Get a subscription",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "cmg2s5b8k0007sub0001abcd"
          }
        ],
        "responses": {
          "200": {
            "description": "The subscription.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscription"
                },
                "example": {
                  "id": "cmg2s5b8k0007sub0001abcd",
                  "status": "active",
                  "frequency": "monthly",
                  "amount": 499,
                  "currency": "ZAR",
                  "gateway": "payfast",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "SUB-ACME-PRO",
                  "metadata": {
                    "plan": "pro"
                  },
                  "createdAt": "2026-09-25T08:14:04.000Z",
                  "updatedAt": "2026-09-25T08:14:04.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "API access (or the feature) is not included in your plan.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "404": {
            "description": "No subscription with this ID in your organisation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "Subscription not found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/subscriptions/{id}/pause": {
      "post": {
        "tags": [
          "Subscriptions"
        ],
        "operationId": "pauseSubscription",
        "summary": "Pause a subscription",
        "description": "Pauses future collections at the gateway and sends `subscription.paused`. Allowed from `active`. Supported on gateways that can pause subscriptions, e.g. PayFast. Repeating a call that already took effect returns 200 with the subscription unchanged (the Idempotency-Key header is not used). An invalid transition returns 409 `invalid_state` and nothing changes. No body is required.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "cmg2s5b8k0007sub0001abcd"
          }
        ],
        "responses": {
          "200": {
            "description": "The subscription (status `paused`).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscription"
                },
                "example": {
                  "id": "cmg2s5b8k0007sub0001abcd",
                  "status": "paused",
                  "frequency": "monthly",
                  "amount": 499,
                  "currency": "ZAR",
                  "gateway": "payfast",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "SUB-ACME-PRO",
                  "metadata": {
                    "plan": "pro"
                  },
                  "createdAt": "2026-09-25T08:14:04.000Z",
                  "updatedAt": "2026-09-25T08:14:04.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Account is read-only (suspended, cancelled or trial ended), or a plan feature/quota is missing.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "account_restricted",
                  "message": "Your free trial has ended. Choose a plan to continue."
                }
              }
            }
          },
          "404": {
            "description": "No subscription with this ID in your organisation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "Subscription not found"
                }
              }
            }
          },
          "409": {
            "description": "`invalid_state`: not allowed from the current status. `unsupported`: the subscription's gateway can't do this, e.g. Netcash Pay Now subscriptions can only be cancelled.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalidState": {
                    "value": {
                      "error": "invalid_state",
                      "message": "Subscription is cancelled"
                    }
                  },
                  "unsupported": {
                    "value": {
                      "error": "unsupported",
                      "message": "Netcash Pay Now does not support pausing subscriptions"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "502": {
            "description": "The gateway rejected or failed the request. Nothing changed; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "gateway_error",
                  "message": "The payment gateway did not accept the request (pausing). Please try again."
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/subscriptions/{id}/resume": {
      "post": {
        "tags": [
          "Subscriptions"
        ],
        "operationId": "resumeSubscription",
        "summary": "Resume a subscription",
        "description": "Resumes a paused subscription at the gateway and sends `subscription.resumed`. Allowed from `paused`. Supported on gateways that can pause subscriptions, e.g. PayFast. Repeating a call that already took effect returns 200 with the subscription unchanged (the Idempotency-Key header is not used). An invalid transition returns 409 `invalid_state` and nothing changes. No body is required.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "cmg2s5b8k0007sub0001abcd"
          }
        ],
        "responses": {
          "200": {
            "description": "The subscription (status `active`).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscription"
                },
                "example": {
                  "id": "cmg2s5b8k0007sub0001abcd",
                  "status": "active",
                  "frequency": "monthly",
                  "amount": 499,
                  "currency": "ZAR",
                  "gateway": "payfast",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "SUB-ACME-PRO",
                  "metadata": {
                    "plan": "pro"
                  },
                  "createdAt": "2026-09-25T08:14:04.000Z",
                  "updatedAt": "2026-09-25T08:14:04.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Account is read-only (suspended, cancelled or trial ended), or a plan feature/quota is missing.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "account_restricted",
                  "message": "Your free trial has ended. Choose a plan to continue."
                }
              }
            }
          },
          "404": {
            "description": "No subscription with this ID in your organisation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "Subscription not found"
                }
              }
            }
          },
          "409": {
            "description": "`invalid_state`: not allowed from the current status. `unsupported`: the subscription's gateway can't do this, e.g. Netcash Pay Now subscriptions can only be cancelled.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalidState": {
                    "value": {
                      "error": "invalid_state",
                      "message": "Subscription is cancelled"
                    }
                  },
                  "unsupported": {
                    "value": {
                      "error": "unsupported",
                      "message": "Netcash Pay Now does not support resuming subscriptions"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "502": {
            "description": "The gateway rejected or failed the request. Nothing changed; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "gateway_error",
                  "message": "The payment gateway did not accept the request (resuming). Please try again."
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/subscriptions/{id}/cancel": {
      "post": {
        "tags": [
          "Subscriptions"
        ],
        "operationId": "cancelSubscription",
        "summary": "Cancel a subscription",
        "description": "Stops future collections at the gateway and sends `subscription.cancelled`. Allowed from `active` or `paused`. Repeating a call that already took effect returns 200 with the subscription unchanged (the Idempotency-Key header is not used). An invalid transition returns 409 `invalid_state` and nothing changes. No body is required.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "cmg2s5b8k0007sub0001abcd"
          }
        ],
        "responses": {
          "200": {
            "description": "The cancelled subscription.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscription"
                },
                "example": {
                  "id": "cmg2s5b8k0007sub0001abcd",
                  "status": "cancelled",
                  "frequency": "monthly",
                  "amount": 499,
                  "currency": "ZAR",
                  "gateway": "payfast",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "SUB-ACME-PRO",
                  "metadata": {
                    "plan": "pro"
                  },
                  "createdAt": "2026-09-25T08:14:04.000Z",
                  "updatedAt": "2026-09-25T08:14:04.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Account is read-only (suspended, cancelled or trial ended), or a plan feature/quota is missing.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "account_restricted",
                  "message": "Your free trial has ended. Choose a plan to continue."
                }
              }
            }
          },
          "404": {
            "description": "No subscription with this ID in your organisation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "Subscription not found"
                }
              }
            }
          },
          "409": {
            "description": "`invalid_state`: not allowed from the current status. `unsupported`: the gateway can't cancel through CentraPoint.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_state",
                  "message": "Subscription is cancelled"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "502": {
            "description": "The gateway rejected or failed the request. Nothing changed; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "gateway_error",
                  "message": "The payment gateway did not accept the cancellation. Please try again."
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/eft-orders": {
      "post": {
        "tags": [
          "EFT orders"
        ],
        "operationId": "createEftOrder",
        "summary": "Create an EFT order",
        "description": "Issues a unique deposit reference and returns your bank details for the payer (201). Requires an enabled Manual EFT gateway, the Payment links feature and API access. Idempotent on `externalReference` for open orders (200).",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EftOrderCreate"
              },
              "example": {
                "amount": 1250,
                "customer": {
                  "externalReference": "CRM-1001"
                },
                "description": "Order #1001",
                "externalReference": "ORDER-1001",
                "metadata": {
                  "cartId": "c_8841"
                },
                "notifyPayer": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An open order with this externalReference already existed; returned unchanged.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EftOrder"
                },
                "example": {
                  "id": "cmg3e1f2t0009eft0001abcd",
                  "reference": "EFT-7K3Q9P2M",
                  "status": "awaiting_payment",
                  "amount": 1250,
                  "currency": "ZAR",
                  "description": "Order #1001",
                  "externalReference": "ORDER-1001",
                  "metadata": {
                    "cartId": "c_8841"
                  },
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001"
                  },
                  "bankDetails": {
                    "bankName": "FNB",
                    "accountName": "Example Traders (Pty) Ltd",
                    "accountNumber": "62000000000",
                    "branchCode": "250655",
                    "accountType": "current"
                  },
                  "paymentReference": "EFT-7K3Q9P2M",
                  "payUrl": "https://app.centrapoint.co.za/eft/cmg3e1f2t0009eft0001abcd.Qm9ndXMtc2lnbmF0dXJlLWV4YW1wbGU",
                  "proofs": [],
                  "rejectionReason": null,
                  "expiresAt": "2026-10-02T08:00:00.000Z",
                  "reviewedAt": null,
                  "paidAt": null,
                  "createdAt": "2026-09-25T08:00:00.000Z"
                }
              }
            }
          },
          "201": {
            "description": "Order created.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EftOrder"
                },
                "example": {
                  "id": "cmg3e1f2t0009eft0001abcd",
                  "reference": "EFT-7K3Q9P2M",
                  "status": "awaiting_payment",
                  "amount": 1250,
                  "currency": "ZAR",
                  "description": "Order #1001",
                  "externalReference": "ORDER-1001",
                  "metadata": {
                    "cartId": "c_8841"
                  },
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001"
                  },
                  "bankDetails": {
                    "bankName": "FNB",
                    "accountName": "Example Traders (Pty) Ltd",
                    "accountNumber": "62000000000",
                    "branchCode": "250655",
                    "accountType": "current"
                  },
                  "paymentReference": "EFT-7K3Q9P2M",
                  "payUrl": "https://app.centrapoint.co.za/eft/cmg3e1f2t0009eft0001abcd.Qm9ndXMtc2lnbmF0dXJlLWV4YW1wbGU",
                  "proofs": [],
                  "rejectionReason": null,
                  "expiresAt": "2026-10-02T08:00:00.000Z",
                  "reviewedAt": null,
                  "paidAt": null,
                  "createdAt": "2026-09-25T08:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed, Manual EFT not set up or not available, unsupported currency, customer not found, expiresAt out of range, or an open order for this externalReference has a different amount. `notifyPayer` without a customer email address.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "message": "Manual EFT is not set up. Add a 'Manual EFT (bank transfer)' gateway with your bank details under Settings > Payment providers."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan lacks the API or payment links, monthly transaction limit reached (`plan_limit`), or account read-only.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "409": {
            "description": "The Idempotency-Key was already used with a different request body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "idempotency_conflict",
                  "message": "This Idempotency-Key was already used with a different request body"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "EFT orders"
        ],
        "operationId": "listEftOrders",
        "summary": "List EFT orders",
        "description": "Newest first.",
        "parameters": [
          {
            "name": "externalReference",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "customerId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "awaiting_payment",
                "awaiting_review",
                "paid",
                "rejected",
                "expired",
                "cancelled"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "EFT orders.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EftOrderList"
                },
                "example": {
                  "data": [
                    {
                      "id": "cmg3e1f2t0009eft0001abcd",
                      "reference": "EFT-7K3Q9P2M",
                      "status": "awaiting_review",
                      "amount": 1250,
                      "currency": "ZAR",
                      "description": "Order #1001",
                      "externalReference": "ORDER-1001",
                      "metadata": {
                        "cartId": "c_8841"
                      },
                      "customer": {
                        "id": "cmg2c0s7t0003cust0001abcd",
                        "externalReference": "CRM-1001"
                      },
                      "bankDetails": {
                        "bankName": "FNB",
                        "accountName": "Example Traders (Pty) Ltd",
                        "accountNumber": "62000000000",
                        "branchCode": "250655",
                        "accountType": "current"
                      },
                      "paymentReference": "EFT-7K3Q9P2M",
                      "payUrl": "https://app.centrapoint.co.za/eft/cmg3e1f2t0009eft0001abcd.Qm9ndXMtc2lnbmF0dXJlLWV4YW1wbGU",
                      "proofs": [
                        {
                          "id": "cmg3p0r0f0011prf0001abcd",
                          "fileName": "proof.pdf",
                          "contentType": "application/pdf",
                          "size": 184213,
                          "source": "payer",
                          "createdAt": "2026-09-25T09:12:00.000Z"
                        }
                      ],
                      "rejectionReason": null,
                      "expiresAt": "2026-10-02T08:00:00.000Z",
                      "reviewedAt": null,
                      "paidAt": null,
                      "createdAt": "2026-09-25T08:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Unknown status.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "message": "status must be one of awaiting_payment, awaiting_review, paid, rejected, expired, cancelled"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not include the API or payment links, or the account is read-only (writes).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/eft-orders/{id}": {
      "get": {
        "tags": [
          "EFT orders"
        ],
        "operationId": "getEftOrder",
        "summary": "Get an EFT order",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "cmg3e1f2t0009eft0001abcd"
          }
        ],
        "responses": {
          "200": {
            "description": "The order.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EftOrder"
                },
                "example": {
                  "id": "cmg3e1f2t0009eft0001abcd",
                  "reference": "EFT-7K3Q9P2M",
                  "status": "awaiting_review",
                  "amount": 1250,
                  "currency": "ZAR",
                  "description": "Order #1001",
                  "externalReference": "ORDER-1001",
                  "metadata": {
                    "cartId": "c_8841"
                  },
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001"
                  },
                  "bankDetails": {
                    "bankName": "FNB",
                    "accountName": "Example Traders (Pty) Ltd",
                    "accountNumber": "62000000000",
                    "branchCode": "250655",
                    "accountType": "current"
                  },
                  "paymentReference": "EFT-7K3Q9P2M",
                  "payUrl": "https://app.centrapoint.co.za/eft/cmg3e1f2t0009eft0001abcd.Qm9ndXMtc2lnbmF0dXJlLWV4YW1wbGU",
                  "proofs": [
                    {
                      "id": "cmg3p0r0f0011prf0001abcd",
                      "fileName": "proof.pdf",
                      "contentType": "application/pdf",
                      "size": 184213,
                      "source": "payer",
                      "createdAt": "2026-09-25T09:12:00.000Z"
                    }
                  ],
                  "rejectionReason": null,
                  "expiresAt": "2026-10-02T08:00:00.000Z",
                  "reviewedAt": null,
                  "paidAt": null,
                  "createdAt": "2026-09-25T08:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not include the API or payment links, or the account is read-only (writes).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "404": {
            "description": "No EFT order with this ID in your organisation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "EFT order not found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/eft-orders/{id}/proof": {
      "post": {
        "tags": [
          "EFT orders"
        ],
        "operationId": "uploadEftProof",
        "summary": "Upload proof of payment",
        "description": "`multipart/form-data` with one `file` field. The type is detected from the file's bytes: PDF, PNG or JPEG only; at most 10 MB; at most 10 files per order. Allowed while the order is awaiting_payment, awaiting_review or rejected (and not past its expiry, unless already in review). Moves the order to `awaiting_review`, notifies your staff and sends `eft.proof_received`. Uploading an identical file again is harmless (deduplicated by content hash). The Idempotency-Key header is not used on this endpoint.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "cmg3e1f2t0009eft0001abcd"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "PDF, PNG or JPEG, max 10 MB."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The order after the upload.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EftOrder"
                },
                "example": {
                  "id": "cmg3e1f2t0009eft0001abcd",
                  "reference": "EFT-7K3Q9P2M",
                  "status": "awaiting_review",
                  "amount": 1250,
                  "currency": "ZAR",
                  "description": "Order #1001",
                  "externalReference": "ORDER-1001",
                  "metadata": {
                    "cartId": "c_8841"
                  },
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001"
                  },
                  "bankDetails": {
                    "bankName": "FNB",
                    "accountName": "Example Traders (Pty) Ltd",
                    "accountNumber": "62000000000",
                    "branchCode": "250655",
                    "accountType": "current"
                  },
                  "paymentReference": "EFT-7K3Q9P2M",
                  "payUrl": "https://app.centrapoint.co.za/eft/cmg3e1f2t0009eft0001abcd.Qm9ndXMtc2lnbmF0dXJlLWV4YW1wbGU",
                  "proofs": [
                    {
                      "id": "cmg3p0r0f0011prf0001abcd",
                      "fileName": "proof.pdf",
                      "contentType": "application/pdf",
                      "size": 184213,
                      "source": "payer",
                      "createdAt": "2026-09-25T09:12:00.000Z"
                    }
                  ],
                  "rejectionReason": null,
                  "expiresAt": "2026-10-02T08:00:00.000Z",
                  "reviewedAt": null,
                  "paidAt": null,
                  "createdAt": "2026-09-25T08:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Missing file, empty or unsupported file type, order not open or expired, or too many proofs.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "message": "Proof must be a PDF, PNG or JPEG file"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not include the API or payment links, or the account is read-only (writes).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "404": {
            "description": "No EFT order with this ID in your organisation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "EFT order not found"
                }
              }
            }
          },
          "413": {
            "description": "File larger than 10 MB.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "message": "File is larger than 10 MB"
                }
              }
            }
          },
          "415": {
            "description": "Body is not multipart/form-data.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "message": "Send multipart/form-data with a 'file' field"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/eft-orders/{id}/cancel": {
      "post": {
        "tags": [
          "EFT orders"
        ],
        "operationId": "cancelEftOrder",
        "summary": "Cancel an EFT order",
        "description": "Cancels an unpaid order (awaiting_payment, awaiting_review or rejected) and its pending transaction, and sends `eft.cancelled` and `payment.cancelled`. Cancelling a cancelled order returns it unchanged. No body is required.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "cmg3e1f2t0009eft0001abcd"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cancelled order.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EftOrder"
                },
                "example": {
                  "id": "cmg3e1f2t0009eft0001abcd",
                  "reference": "EFT-7K3Q9P2M",
                  "status": "cancelled",
                  "amount": 1250,
                  "currency": "ZAR",
                  "description": "Order #1001",
                  "externalReference": "ORDER-1001",
                  "metadata": {
                    "cartId": "c_8841"
                  },
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001"
                  },
                  "bankDetails": {
                    "bankName": "FNB",
                    "accountName": "Example Traders (Pty) Ltd",
                    "accountNumber": "62000000000",
                    "branchCode": "250655",
                    "accountType": "current"
                  },
                  "paymentReference": "EFT-7K3Q9P2M",
                  "payUrl": "https://app.centrapoint.co.za/eft/cmg3e1f2t0009eft0001abcd.Qm9ndXMtc2lnbmF0dXJlLWV4YW1wbGU",
                  "proofs": [],
                  "rejectionReason": null,
                  "expiresAt": "2026-10-02T08:00:00.000Z",
                  "reviewedAt": null,
                  "paidAt": null,
                  "createdAt": "2026-09-25T08:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "The order is paid or expired.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "message": "EFT order is paid"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not include the API or payment links, or the account is read-only (writes).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "404": {
            "description": "No EFT order with this ID in your organisation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "EFT order not found"
                }
              }
            }
          },
          "409": {
            "description": "The Idempotency-Key was already used with a different request body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "idempotency_conflict",
                  "message": "This Idempotency-Key was already used with a different request body"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/transactions/{reference}": {
      "get": {
        "tags": [
          "Transactions"
        ],
        "operationId": "getTransaction",
        "summary": "Get a transaction by reference",
        "description": "Returns the current state of a payment in your organisation, looked up by its CentraPoint payment reference. Transactions in other organisations are reported as not found.",
        "parameters": [
          {
            "name": "reference",
            "in": "path",
            "required": true,
            "description": "CentraPoint payment reference, e.g. `CP-20260925-9F3A1C7B`. Recurring renewals use `<original reference>-<gateway payment id>`; debit order collections use `<mandate account reference>-<YYYYMMDD>`, e.g. `DO7F3A1C9B2E-20261001`.",
            "schema": {
              "type": "string"
            },
            "example": "CP-20260925-9F3A1C7B"
          }
        ],
        "responses": {
          "200": {
            "description": "The transaction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transaction"
                },
                "example": {
                  "reference": "CP-20260925-9F3A1C7B",
                  "status": "partially_refunded",
                  "amount": 499,
                  "fee": 14.47,
                  "net": 484.53,
                  "currency": "ZAR",
                  "type": "once",
                  "gateway": "payfast",
                  "description": "Website hosting - October",
                  "providerRef": "2458761",
                  "payerEmail": "client@example.com",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "ORDER-1001",
                  "eftOrderId": null,
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "renewal": false,
                  "parentReference": null,
                  "subscriptionId": null,
                  "refundedAmount": 100,
                  "refunds": [
                    {
                      "refundNumber": "REF-7C21A9F0",
                      "amount": 100,
                      "currency": "ZAR",
                      "status": "processed",
                      "processedAt": "2026-09-26T09:30:00.000Z",
                      "createdAt": "2026-09-26T09:29:41.000Z"
                    }
                  ],
                  "invoice": null,
                  "paidAt": "2026-09-25T08:14:03.512Z",
                  "createdAt": "2026-09-25T08:12:47.020Z"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "API access is not included in your plan.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "API access is not included in your plan"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "404": {
            "description": "No transaction with this reference exists in your organisation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "Transaction not found"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/reconciliation/runs": {
      "get": {
        "tags": [
          "Reconciliation"
        ],
        "operationId": "listReconciliationRuns",
        "summary": "List reconciliation runs",
        "description": "Statement imports (Netcash statements and CSV uploads) with their results, newest first by start time. Requires the Reconciliation feature (Business plan and above).",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "YYYY-MM-DD (South African time): runs started on or after this day.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "YYYY-MM-DD (South African time): runs started on or before this day.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Runs.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReconciliationRunList"
                },
                "example": {
                  "data": [
                    {
                      "id": "cmg3r7c0n0013run0001abcd",
                      "source": "netcash_statement",
                      "statementDate": "2026-09-24T22:00:00.000Z",
                      "periodStart": "2026-09-23T22:00:00.000Z",
                      "periodEnd": "2026-09-24T22:00:00.000Z",
                      "status": "exceptions",
                      "lineCount": 42,
                      "matched": 38,
                      "exceptions": 2,
                      "totals": {
                        "receipts": 48250.5,
                        "fees": 612.4,
                        "refunds": 499,
                        "payouts": 45000,
                        "unpaids": 350
                      },
                      "openingBalance": 1203.55,
                      "closingBalance": 3342.71,
                      "error": null,
                      "createdAt": "2026-09-25T02:00:03.000Z",
                      "completedAt": "2026-09-25T02:04:11.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad date.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "message": "from and to must be YYYY-MM-DD"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not include the API or reconciliation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/reconciliation/runs/{id}": {
      "get": {
        "tags": [
          "Reconciliation"
        ],
        "operationId": "getReconciliationRun",
        "summary": "Get a reconciliation run",
        "description": "The run summary plus its items: every statement line except ignored ones (fees, payouts and balances are only in `totals`), and for completed Netcash statement runs, `not_received` items.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "cmg3r7c0n0013run0001abcd"
          }
        ],
        "responses": {
          "200": {
            "description": "The run with items.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReconciliationRunDetail"
                },
                "example": {
                  "id": "cmg3r7c0n0013run0001abcd",
                  "source": "netcash_statement",
                  "statementDate": "2026-09-24T22:00:00.000Z",
                  "periodStart": "2026-09-23T22:00:00.000Z",
                  "periodEnd": "2026-09-24T22:00:00.000Z",
                  "status": "exceptions",
                  "lineCount": 42,
                  "matched": 38,
                  "exceptions": 2,
                  "totals": {
                    "receipts": 48250.5,
                    "fees": 612.4,
                    "refunds": 499,
                    "payouts": 45000,
                    "unpaids": 350
                  },
                  "openingBalance": 1203.55,
                  "closingBalance": 3342.71,
                  "error": null,
                  "createdAt": "2026-09-25T02:00:03.000Z",
                  "completedAt": "2026-09-25T02:04:11.000Z",
                  "items": [
                    {
                      "status": "matched",
                      "reference": "CP-20260924-9F3A1C7B",
                      "externalReference": "ORDER-1001",
                      "expectedAmount": 499,
                      "statementAmount": 499,
                      "date": "2026-09-24T00:00:00.000Z",
                      "description": "PAYNOW CP-20260924-9F3A1C7B",
                      "note": null
                    },
                    {
                      "status": "amount_mismatch",
                      "reference": "CP-20260924-41D07E2A",
                      "externalReference": "ORDER-1002",
                      "expectedAmount": 1200,
                      "statementAmount": 1100,
                      "date": "2026-09-24T00:00:00.000Z",
                      "description": "PAYNOW CP-20260924-41D07E2A",
                      "note": "Amount differs: statement 1100.00 vs transaction 1200.00"
                    },
                    {
                      "status": "not_received",
                      "reference": "CP-20260920-7C21A9F0",
                      "externalReference": "ORDER-0990",
                      "expectedAmount": 250,
                      "statementAmount": null,
                      "date": null,
                      "description": "Website hosting",
                      "note": "Completed in CentraPoint but not on the statement after 3 days"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Plan does not include the API or reconciliation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "This feature is not included in your plan"
                }
              }
            }
          },
          "404": {
            "description": "No run with this ID in your organisation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found",
                  "message": "Reconciliation run not found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    },
    "/api/v1/accounting/referral-links": {
      "get": {
        "tags": [
          "Accounting"
        ],
        "operationId": "listAccountingReferralLinks",
        "summary": "List accounting referral links",
        "description": "Returns tracked sign-up links for the accounting systems CentraPoint partners with, so you can show a \"Don't have Xero? Sign up\" link in your own app. Only active partner programmes are returned; the list can be empty. Each `url` is specific to your organisation. Available on any plan with API access: signing up for an accounting system does not require CentraPoint's accounting sync feature.",
        "responses": {
          "200": {
            "description": "Active referral links.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferralLinkList"
                },
                "example": {
                  "data": [
                    {
                      "connector": "xero",
                      "name": "Xero",
                      "program": "Xero App Partner",
                      "url": "https://app.centrapoint.co.za/r/accounting/xero?s=cpQ2VudHJhUG9pbnQx.bG9uZy1zaWduYXR1cmUx"
                    },
                    {
                      "connector": "zoho_books",
                      "name": "Zoho Books",
                      "program": "Zoho Affiliate",
                      "url": "https://app.centrapoint.co.za/r/accounting/zoho_books?s=cpQ2VudHJhUG9pbnQx.c2Vjb25kLXNpZ25hdHVyZQ"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "API access is not included in your plan.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_restricted",
                  "message": "API access is not included in your plan"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again or contact support."
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "cp_<8 hex>_<secret>",
        "description": "API key created in the dashboard under Settings → API keys, sent as `Authorization: Bearer cp_xxxxxxxx_<secret>`. Keys are scoped to one organisation. Rate limits apply per key (120 requests/minute); see the X-RateLimit-* response headers."
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "The API key is missing, malformed, unknown or revoked. No rate-limit headers are sent, because the key is not known.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "missing": {
                "value": {
                  "error": "unauthorized",
                  "message": "Missing or malformed API key"
                }
              },
              "invalid": {
                "value": {
                  "error": "unauthorized",
                  "message": "Invalid API key"
                }
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "More than 120 requests in the current 60-second window for this API key, or more than 600 from one client IP before authentication (message `Too many requests from this network`; headers then describe the IP window).",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "rate_limited",
              "message": "Rate limit of 120 requests per minute exceeded for this API key"
            }
          }
        }
      },
      "InternalError": {
        "description": "Unexpected server error. The details are logged by CentraPoint; the message is generic.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "internal_error",
              "message": "An unexpected error occurred. Please try again or contact support."
            }
          }
        }
      },
      "PlanRestrictedApi": {
        "description": "API access is not included in your plan.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "plan_restricted",
              "message": "API access is not included in your plan"
            }
          }
        }
      }
    },
    "schemas": {
      "CreatePaymentLinkRequest": {
        "type": "object",
        "required": [
          "title",
          "amount"
        ],
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "Shown to the payer. Leading and trailing whitespace is trimmed before the length check.",
            "example": "Website hosting - October"
          },
          "description": {
            "type": "string",
            "maxLength": 255,
            "description": "Optional longer text shown under the title on the payment page."
          },
          "amount": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 10000000,
            "description": "Amount in major units (e.g. 499.00 rand). Rounded to 2 decimal places. Gateways may enforce their own minimums or whole-unit amounts.",
            "example": 499
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Za-z]{3}$",
            "default": "ZAR",
            "description": "ISO 4217 currency code, upper-cased by the server. Must be a valid ISO 4217 code and accepted by at least one of your enabled checkout gateways (or, for once-off links, by your Manual EFT gateway); otherwise the request fails with 400 invalid_request.",
            "example": "ZAR"
          },
          "externalReference": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "Your own identifier for this link, e.g. your order or invoice number. Trimmed before validation. Not required to be unique. Use it with `GET /api/v1/payment-links?externalReference=` to find the link and its payment references later.",
            "example": "ORDER-1001"
          },
          "returnUrl": {
            "type": "string",
            "format": "uri",
            "maxLength": 1000,
            "description": "Where to send the payer after a final outcome. Must be https and on an origin allow-listed under Settings → API keys → Allowed return URLs. CentraPoint appends `reference` and `status` (`complete`, `failed` or `cancelled`) query parameters. Never trust the redirect: confirm server-side.",
            "example": "https://shop.example.co.za/checkout/done"
          },
          "metadata": {
            "$ref": "#/components/schemas/MetadataInput"
          },
          "customerId": {
            "type": "string",
            "description": "Attach an existing customer (takes precedence over customerEmail)."
          },
          "recurring": {
            "type": "object",
            "required": [
              "frequency"
            ],
            "description": "Start a gateway-managed subscription when the link is paid. Requires a plan with recurring billing and an enabled gateway that supports it (PayFast, Netcash Pay Now); only such gateways are offered to the payer.",
            "properties": {
              "frequency": {
                "type": "string",
                "enum": [
                  "monthly",
                  "quarterly",
                  "biannually",
                  "annually"
                ]
              },
              "amount": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 10000000,
                "description": "Amount of each later collection. Defaults to the link amount."
              }
            }
          },
          "productId": {
            "type": "string",
            "description": "ID of a product in your organisation. Recurring products start a gateway-managed subscription at checkout (plan and gateway permitting)."
          },
          "invoiceId": {
            "type": "string",
            "description": "ID of an invoice in your organisation. Successful payments are applied to the invoice."
          },
          "customerEmail": {
            "type": "string",
            "format": "email",
            "description": "Attach the link to a customer. Matched case-insensitively against existing customers; a new customer is created if none exists."
          },
          "customerFirstName": {
            "type": "string",
            "maxLength": 100,
            "description": "Used only when a new customer is created. Defaults to the part of the email before @."
          },
          "customerLastName": {
            "type": "string",
            "maxLength": 100,
            "description": "Used only when a new customer is created."
          },
          "singleUse": {
            "type": "boolean",
            "default": true,
            "description": "When true, the link is deactivated after its first successful payment."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with `Z` or a UTC offset (e.g. `2026-10-31T23:59:59+02:00`), including seconds. Must be in the future and at most 1 year ahead. After this moment the link cannot be paid.",
            "example": "2026-10-31T23:59:59+02:00"
          }
        },
        "description": "Unknown fields are ignored."
      },
      "PaymentLink": {
        "type": "object",
        "required": [
          "id",
          "url",
          "title",
          "description",
          "amount",
          "currency",
          "externalReference",
          "singleUse",
          "active",
          "expiresAt",
          "paidCount",
          "createdAt",
          "returnUrl",
          "metadata",
          "recurring"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Payment link ID."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Hosted payment page to send to the payer: https://app.centrapoint.co.za/pay/{token}."
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "amount": {
            "type": "number",
            "description": "Amount after rounding to 2 decimal places."
          },
          "currency": {
            "type": "string",
            "description": "Upper-case ISO 4217 code."
          },
          "externalReference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your identifier, as supplied when the link was created."
          },
          "returnUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          },
          "recurring": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "required": [
                  "frequency",
                  "amount"
                ],
                "properties": {
                  "frequency": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "number"
                  }
                }
              }
            ]
          },
          "singleUse": {
            "type": "boolean",
            "description": "Whether the link deactivates after its first successful payment."
          },
          "active": {
            "type": "boolean",
            "description": "False once deactivated (in the dashboard, or after a single-use link is paid)."
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "paidCount": {
            "type": "integer",
            "description": "Number of successful payments made through the link."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Transaction": {
        "type": "object",
        "required": [
          "reference",
          "status",
          "amount",
          "fee",
          "net",
          "currency",
          "type",
          "gateway",
          "description",
          "providerRef",
          "payerEmail",
          "paymentLinkId",
          "externalReference",
          "eftOrderId",
          "metadata",
          "renewal",
          "parentReference",
          "subscriptionId",
          "refundedAmount",
          "refunds",
          "invoice",
          "paidAt",
          "createdAt"
        ],
        "properties": {
          "reference": {
            "type": "string",
            "description": "CentraPoint payment reference."
          },
          "status": {
            "$ref": "#/components/schemas/TransactionStatus"
          },
          "amount": {
            "type": "number",
            "description": "Gross amount in major units."
          },
          "fee": {
            "type": [
              "number",
              "null"
            ],
            "description": "Gateway fee, when the gateway reports it."
          },
          "net": {
            "type": [
              "number",
              "null"
            ],
            "description": "Amount after the gateway fee, when reported."
          },
          "currency": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "once",
              "recurring",
              "invoice",
              "debit_order",
              "eft"
            ],
            "description": "`invoice` when the link was for an invoice, `recurring` for subscription charges, `debit_order` for Netcash debit order collections, otherwise `once`. `eft` for Manual EFT orders."
          },
          "gateway": {
            "type": "string",
            "description": "Gateway type that processed the payment, e.g. `payfast`, `paystack`, `mpesa`, `netcash`.",
            "example": "payfast"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Usually the payment link title."
          },
          "providerRef": {
            "type": [
              "string",
              "null"
            ],
            "description": "The gateway's own ID for the payment (e.g. PayFast pf_payment_id)."
          },
          "payerEmail": {
            "type": [
              "string",
              "null"
            ]
          },
          "paymentLinkId": {
            "type": [
              "string",
              "null"
            ],
            "description": "ID of the payment link the payment was made through, if any."
          },
          "externalReference": {
            "type": [
              "string",
              "null"
            ],
            "description": "The payment link's externalReference, or the EFT order's."
          },
          "eftOrderId": {
            "type": [
              "string",
              "null"
            ],
            "description": "EFT order the payment belongs to (Manual EFT)."
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata",
            "description": "The payment link's metadata, or the EFT order's. Always an object (`{}` when none)."
          },
          "renewal": {
            "type": "boolean",
            "description": "True for later collections of a subscription."
          },
          "parentReference": {
            "type": [
              "string",
              "null"
            ],
            "description": "For renewals: reference of the first payment."
          },
          "subscriptionId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Subscription started by the payment link, if any."
          },
          "refundedAmount": {
            "type": "number",
            "description": "Total of non-failed refunds, 0 if none."
          },
          "refunds": {
            "type": "array",
            "description": "Refunds recorded for this payment, oldest first. Failed refunds are excluded.",
            "items": {
              "$ref": "#/components/schemas/Refund"
            }
          },
          "invoice": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "required": [
                  "id",
                  "number",
                  "invoiceNumber",
                  "status",
                  "externalReference"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "number": {
                    "type": "string",
                    "example": "INV-000042"
                  },
                  "invoiceNumber": {
                    "type": "string",
                    "description": "Same as `number`; kept for compatibility.",
                    "deprecated": true
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "sent",
                      "paid",
                      "overdue",
                      "cancelled"
                    ]
                  },
                  "externalReference": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            ]
          },
          "paidAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Set when the transaction becomes complete."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TransactionStatus": {
        "type": "string",
        "enum": [
          "pending",
          "complete",
          "failed",
          "cancelled",
          "refunded",
          "partially_refunded"
        ],
        "description": "`complete` is the only status that means money was received. `refunded` / `partially_refunded` are set when a refund is recorded in the dashboard."
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorCode"
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation. Do not parse; it may change."
          },
          "issues": {
            "type": "array",
            "description": "Present on validation failures. One entry per problem.",
            "items": {
              "$ref": "#/components/schemas/ValidationIssue"
            }
          }
        }
      },
      "ErrorCode": {
        "type": "string",
        "enum": [
          "invalid_request",
          "unauthorized",
          "plan_restricted",
          "plan_limit",
          "account_restricted",
          "not_found",
          "idempotency_conflict",
          "unsupported",
          "rate_limited",
          "internal_error",
          "gateway_error",
          "invalid_state"
        ]
      },
      "ValidationIssue": {
        "type": "object",
        "required": [
          "code",
          "path",
          "message"
        ],
        "additionalProperties": true,
        "properties": {
          "code": {
            "type": "string",
            "description": "e.g. invalid_type, too_small, too_big, invalid_format."
          },
          "path": {
            "type": "array",
            "items": {
              "type": [
                "string",
                "integer"
              ]
            },
            "description": "Location of the offending field, e.g. [\"amount\"]. Empty when the whole body is invalid."
          },
          "message": {
            "type": "string"
          }
        }
      },
      "LinkPayment": {
        "type": "object",
        "required": [
          "reference",
          "status",
          "amount",
          "currency",
          "paidAt",
          "createdAt"
        ],
        "properties": {
          "reference": {
            "type": "string",
            "description": "Payment reference. Use it with `GET /api/v1/transactions/{reference}`."
          },
          "status": {
            "$ref": "#/components/schemas/TransactionStatus"
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "paidAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PaymentLinkWithPayments": {
        "type": "object",
        "required": [
          "id",
          "url",
          "title",
          "description",
          "amount",
          "currency",
          "externalReference",
          "singleUse",
          "active",
          "expiresAt",
          "paidCount",
          "createdAt",
          "payments",
          "returnUrl",
          "metadata",
          "recurring"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Payment link ID."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Hosted payment page to send to the payer: https://app.centrapoint.co.za/pay/{token}."
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "amount": {
            "type": "number",
            "description": "Amount after rounding to 2 decimal places."
          },
          "currency": {
            "type": "string",
            "description": "Upper-case ISO 4217 code."
          },
          "externalReference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your identifier, as supplied when the link was created."
          },
          "returnUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          },
          "recurring": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "required": [
                  "frequency",
                  "amount"
                ],
                "properties": {
                  "frequency": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "number"
                  }
                }
              }
            ]
          },
          "singleUse": {
            "type": "boolean",
            "description": "Whether the link deactivates after its first successful payment."
          },
          "active": {
            "type": "boolean",
            "description": "False once deactivated (in the dashboard, or after a single-use link is paid)."
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "paidCount": {
            "type": "integer",
            "description": "Number of successful payments made through the link."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "payments": {
            "type": "array",
            "description": "Checkout attempts made through the link, newest first. Each attempt is a transaction with its own reference.",
            "items": {
              "$ref": "#/components/schemas/LinkPayment"
            }
          },
          "subscription": {
            "description": "The latest subscription started by this link, or null.",
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "required": [
                  "id",
                  "status"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "status": {
                    "type": "string"
                  }
                }
              }
            ]
          }
        }
      },
      "PaymentLinkList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PaymentLinkWithPayments"
            }
          }
        }
      },
      "Refund": {
        "type": "object",
        "required": [
          "refundNumber",
          "amount",
          "currency",
          "status",
          "processedAt",
          "createdAt"
        ],
        "properties": {
          "refundNumber": {
            "type": "string",
            "example": "REF-7C21A9F0"
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processed"
            ],
            "description": "Failed refunds are not returned."
          },
          "processedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "required": [
          "id",
          "type",
          "created",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique event ID (`evt_…`). Stable across retries, resends and endpoints; de-duplicate on it.",
            "example": "evt_3f9a1c7b2e4d6f8091a2b3c4"
          },
          "type": {
            "type": "string",
            "enum": [
              "payment.complete",
              "payment.failed",
              "payment.cancelled",
              "payment.refunded",
              "payment.partially_refunded"
            ]
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "$ref": "#/components/schemas/WebhookPaymentData"
          }
        }
      },
      "WebhookPaymentData": {
        "type": "object",
        "required": [
          "reference",
          "status",
          "amount",
          "currency",
          "type",
          "gateway",
          "providerRef",
          "paymentLinkId",
          "externalReference",
          "eftOrderId",
          "invoice",
          "customer",
          "metadata",
          "renewal",
          "parentReference",
          "subscriptionId",
          "refundedAmount",
          "paidAt"
        ],
        "properties": {
          "reference": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/TransactionStatus"
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "once",
              "recurring",
              "invoice",
              "debit_order",
              "eft"
            ]
          },
          "gateway": {
            "type": "string"
          },
          "providerRef": {
            "type": [
              "string",
              "null"
            ]
          },
          "paymentLinkId": {
            "type": [
              "string",
              "null"
            ]
          },
          "externalReference": {
            "type": [
              "string",
              "null"
            ],
            "description": "The payment link's externalReference, or the EFT order's."
          },
          "eftOrderId": {
            "type": [
              "string",
              "null"
            ],
            "description": "EFT order the payment belongs to (Manual EFT)."
          },
          "invoice": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "required": [
                  "id",
                  "number",
                  "status",
                  "externalReference"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "number": {
                    "type": "string"
                  },
                  "status": {
                    "type": "string"
                  },
                  "externalReference": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            ]
          },
          "customer": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "required": [
                  "id",
                  "externalReference"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "externalReference": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            ]
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata",
            "description": "The payment link's metadata, or the EFT order's."
          },
          "renewal": {
            "type": "boolean"
          },
          "parentReference": {
            "type": [
              "string",
              "null"
            ]
          },
          "subscriptionId": {
            "type": [
              "string",
              "null"
            ]
          },
          "refundedAmount": {
            "type": "number"
          },
          "paidAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ReferralLink": {
        "type": "object",
        "required": [
          "connector",
          "name",
          "program",
          "url"
        ],
        "properties": {
          "connector": {
            "type": "string",
            "description": "Accounting connector type, e.g. `xero`, `quickbooks`, `zoho_books`, `sage_one_za`.",
            "example": "xero"
          },
          "name": {
            "type": "string",
            "description": "Display name of the accounting system.",
            "example": "Xero"
          },
          "program": {
            "type": "string",
            "description": "Name of the partner programme.",
            "example": "Xero App Partner"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Signed CentraPoint redirect (`/r/accounting/{connector}?s=…`) that records the click for your organisation and forwards to the vendor's sign-up page. Works without a CentraPoint login; do not modify it.",
            "example": "https://app.centrapoint.co.za/r/accounting/xero?s=cpQ2VudHJhUG9pbnQx.bG9uZy1zaWduYXR1cmUx"
          }
        }
      },
      "ReferralLinkList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReferralLink"
            }
          }
        }
      },
      "Metadata": {
        "type": "object",
        "description": "Your own key/value data. Flat map of strings (numbers and booleans are converted to strings; null values are dropped). Up to 50 keys; keys 1–40 characters of letters, digits, `_ . : -`; values up to 500 characters; at most 4000 characters as JSON. Returned as `{}` when empty.",
        "additionalProperties": {
          "type": "string",
          "maxLength": 500
        },
        "maxProperties": 50,
        "example": {
          "crmId": "0061x00000AbCdE",
          "plan": "pro"
        }
      },
      "MetadataInput": {
        "type": "object",
        "description": "Your own key/value data. Flat map of strings (numbers and booleans are converted to strings; null values are dropped). Up to 50 keys; keys 1–40 characters of letters, digits, `_ . : -`; values up to 500 characters; at most 4000 characters as JSON. Returned as `{}` when empty.",
        "additionalProperties": {
          "type": [
            "string",
            "number",
            "boolean",
            "null"
          ]
        },
        "maxProperties": 50,
        "example": {
          "crmId": "0061x00000AbCdE",
          "plan": "pro"
        }
      },
      "Customer": {
        "type": "object",
        "required": [
          "id",
          "accountCode",
          "externalReference",
          "email",
          "firstName",
          "lastName",
          "company",
          "vatNumber",
          "phone",
          "address",
          "active",
          "metadata",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "accountCode": {
            "type": "string",
            "description": "Customer account code assigned by CentraPoint."
          },
          "externalReference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your ID for the customer."
          },
          "email": {
            "type": "string"
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": [
              "string",
              "null"
            ]
          },
          "company": {
            "type": [
              "string",
              "null"
            ]
          },
          "vatNumber": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "active": {
            "type": "boolean"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CustomerCreate": {
        "type": "object",
        "required": [
          "email",
          "firstName"
        ],
        "properties": {
          "externalReference": {
            "type": "string",
            "minLength": 1,
            "maxLength": 190,
            "description": "Your ID. When set, the request is an upsert keyed on it."
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 200,
            "description": "Stored lower-cased."
          },
          "firstName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "lastName": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100
          },
          "company": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "vatNumber": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 30
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 30
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000
          },
          "metadata": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/MetadataInput"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "CustomerUpdate": {
        "type": "object",
        "additionalProperties": false,
        "description": "Partial update. Omitted fields are unchanged; `null` clears an optional field. Unknown fields are rejected.",
        "properties": {
          "externalReference": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 190,
            "description": "Must not be used by another customer."
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 200,
            "description": "Stored lower-cased."
          },
          "firstName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "lastName": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100
          },
          "company": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "vatNumber": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 30
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 30
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000
          },
          "metadata": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/MetadataInput"
              },
              {
                "type": "null"
              }
            ]
          },
          "active": {
            "type": "boolean"
          }
        }
      },
      "CustomerList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Customer"
            }
          }
        }
      },
      "InvoiceLine": {
        "type": "object",
        "required": [
          "description",
          "quantity",
          "unitPrice",
          "taxRate",
          "amount"
        ],
        "properties": {
          "description": {
            "type": "string"
          },
          "quantity": {
            "type": "number"
          },
          "unitPrice": {
            "type": "number",
            "description": "Excluding VAT."
          },
          "taxRate": {
            "type": "number",
            "description": "VAT percentage for this line."
          },
          "amount": {
            "type": "number",
            "description": "quantity × unitPrice, excluding VAT."
          }
        }
      },
      "Invoice": {
        "type": "object",
        "required": [
          "id",
          "number",
          "status",
          "externalReference",
          "customer",
          "currency",
          "issueDate",
          "dueDate",
          "lines",
          "subtotal",
          "taxAmount",
          "total",
          "amountPaid",
          "amountDue",
          "paidAt",
          "payUrl",
          "notes",
          "metadata",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "number": {
            "type": "string",
            "example": "INV-000042"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "sent",
              "overdue",
              "partially_paid",
              "paid",
              "cancelled"
            ],
            "description": "`partially_paid` is reported for draft/sent/overdue invoices that have received some money."
          },
          "externalReference": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer": {
            "type": "object",
            "required": [
              "id",
              "externalReference",
              "email"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "externalReference": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": "string"
              }
            }
          },
          "currency": {
            "type": "string"
          },
          "issueDate": {
            "type": "string",
            "format": "date-time"
          },
          "dueDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InvoiceLine"
            }
          },
          "subtotal": {
            "type": "number"
          },
          "taxAmount": {
            "type": "number"
          },
          "total": {
            "type": "number"
          },
          "amountPaid": {
            "type": "number"
          },
          "amountDue": {
            "type": "number",
            "description": "total − amountPaid."
          },
          "paidAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "payUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Hosted payment page for the outstanding balance, or null (paid/cancelled, no pay link yet, or neither a checkout gateway nor Manual EFT accepts the currency)."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "InvoiceCreate": {
        "type": "object",
        "required": [
          "lines"
        ],
        "properties": {
          "customerId": {
            "type": "string",
            "description": "The customer's CentraPoint ID. Send exactly one of `customerId` or `customer`."
          },
          "customer": {
            "description": "The customer, by CentraPoint ID or by your externalReference. Send exactly one of `customerId` or `customer`.",
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "id"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  }
                }
              },
              {
                "type": "object",
                "required": [
                  "externalReference"
                ],
                "properties": {
                  "externalReference": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 190
                  }
                }
              }
            ]
          },
          "externalReference": {
            "type": "string",
            "minLength": 1,
            "maxLength": 190,
            "description": "Your ID for the invoice. If an invoice with this reference exists, it is returned unchanged with 200."
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Za-z]{3}$",
            "default": "ZAR",
            "description": "ISO 4217 code (validated). No gateway is required: invoices can be paid offline, but `payUrl` is only created when a gateway accepts the currency."
          },
          "issueDate": {
            "type": "string",
            "format": "date",
            "description": "YYYY-MM-DD (South African time). Default today.",
            "example": "2026-10-01"
          },
          "dueDate": {
            "type": "string",
            "format": "date",
            "description": "YYYY-MM-DD (South African time, end of day). Default: today plus your invoice due days setting.",
            "example": "2026-10-01"
          },
          "lines": {
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "items": {
              "type": "object",
              "required": [
                "description",
                "quantity",
                "unitPrice"
              ],
              "properties": {
                "description": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 1000
                },
                "quantity": {
                  "type": "number",
                  "exclusiveMinimum": 0,
                  "maximum": 1000000
                },
                "unitPrice": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100000000,
                  "description": "Excluding VAT."
                },
                "taxRate": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100,
                  "description": "VAT %. Defaults to your organisation's VAT rate."
                }
              }
            }
          },
          "notes": {
            "type": "string",
            "maxLength": 4000,
            "description": "Defaults to your invoice terms."
          },
          "metadata": {
            "$ref": "#/components/schemas/MetadataInput"
          },
          "send": {
            "type": "boolean",
            "default": false,
            "description": "Create as `sent` and email it to the customer (with a pay link when possible). Otherwise it is created as a draft."
          }
        },
        "description": "Send exactly one of `customerId` or `customer`."
      },
      "InvoiceList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Invoice"
            }
          }
        }
      },
      "InvoicePaymentCreate": {
        "type": "object",
        "required": [
          "amount",
          "method"
        ],
        "properties": {
          "amount": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 100000000,
            "description": "Must not exceed the outstanding balance."
          },
          "method": {
            "type": "string",
            "enum": [
              "eft",
              "cash",
              "card",
              "wallet",
              "other"
            ]
          },
          "reference": {
            "type": "string",
            "maxLength": 190,
            "description": "Your reference, e.g. a bank statement reference."
          },
          "paidAt": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 with Z or an offset. Default now. Cannot be in the future."
          }
        }
      },
      "InvoiceCancel": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "maxLength": 500,
            "description": "Appended to the invoice notes."
          }
        }
      },
      "Subscription": {
        "type": "object",
        "required": [
          "id",
          "status",
          "frequency",
          "amount",
          "currency",
          "gateway",
          "paymentLinkId",
          "externalReference",
          "metadata",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused",
              "cancelled"
            ]
          },
          "frequency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "monthly",
              "quarterly",
              "biannually",
              "annually",
              null
            ]
          },
          "amount": {
            "type": [
              "number",
              "null"
            ],
            "description": "Amount of each collection."
          },
          "currency": {
            "type": "string"
          },
          "gateway": {
            "type": "string",
            "description": "Gateway that bills the subscription, e.g. `payfast`, `netcash_paynow`."
          },
          "paymentLinkId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The recurring payment link that started it."
          },
          "externalReference": {
            "type": [
              "string",
              "null"
            ],
            "description": "That link's externalReference."
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SubscriptionList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Subscription"
            }
          }
        }
      },
      "InvoiceWebhookEvent": {
        "type": "object",
        "required": [
          "id",
          "type",
          "created",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique event ID (`evt_…`)."
          },
          "type": {
            "type": "string",
            "enum": [
              "invoice.paid",
              "invoice.partially_paid",
              "invoice.cancelled"
            ]
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "$ref": "#/components/schemas/Invoice"
          }
        }
      },
      "EftOrder": {
        "type": "object",
        "required": [
          "id",
          "reference",
          "status",
          "amount",
          "currency",
          "description",
          "externalReference",
          "metadata",
          "customer",
          "bankDetails",
          "paymentReference",
          "proofs",
          "rejectionReason",
          "expiresAt",
          "reviewedAt",
          "paidAt",
          "createdAt",
          "payUrl"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "reference": {
            "type": "string",
            "description": "Unique deposit reference, e.g. `EFT-7K3Q9P2M` (prefix configurable, 8 characters without 0/O/1/I/L). Also the payment reference of the order's transaction."
          },
          "status": {
            "type": "string",
            "enum": [
              "awaiting_payment",
              "awaiting_review",
              "paid",
              "rejected",
              "expired",
              "cancelled"
            ]
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "externalReference": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          },
          "customer": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "required": [
                  "id",
                  "externalReference"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "externalReference": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            ]
          },
          "bankDetails": {
            "type": "object",
            "required": [
              "bankName",
              "accountName",
              "accountNumber",
              "branchCode",
              "accountType"
            ],
            "description": "Your bank account from the Manual EFT gateway. Show these to the payer.",
            "properties": {
              "bankName": {
                "type": "string"
              },
              "accountName": {
                "type": "string"
              },
              "accountNumber": {
                "type": "string"
              },
              "branchCode": {
                "type": "string"
              },
              "accountType": {
                "type": "string",
                "enum": [
                  "current",
                  "savings",
                  "transmission"
                ]
              }
            }
          },
          "paymentReference": {
            "type": "string",
            "description": "The reference the payer must use on the transfer (same as `reference`)."
          },
          "payUrl": {
            "type": "string",
            "format": "uri",
            "description": "Signed public payer page with the bank details, the reference and a proof-of-payment upload. No login needed; share it instead of building your own upload. A tampered link returns 404."
          },
          "proofs": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "fileName",
                "contentType",
                "size",
                "source",
                "createdAt"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "fileName": {
                  "type": "string"
                },
                "contentType": {
                  "type": "string",
                  "enum": [
                    "application/pdf",
                    "image/png",
                    "image/jpeg"
                  ]
                },
                "size": {
                  "type": "integer"
                },
                "source": {
                  "type": "string",
                  "enum": [
                    "api",
                    "dashboard",
                    "payer"
                  ],
                  "description": "Who uploaded it: your system via the API, staff in the dashboard, or the payer on the payer page."
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          },
          "rejectionReason": {
            "type": [
              "string",
              "null"
            ]
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "reviewedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "paidAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "EftOrderList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EftOrder"
            }
          }
        }
      },
      "EftOrderCreate": {
        "type": "object",
        "required": [
          "amount"
        ],
        "description": "Send `customerId` or `customer`, not both. Unknown fields are ignored.",
        "properties": {
          "amount": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 100000000
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Za-z]{3}$",
            "default": "ZAR",
            "description": "One of ZAR, NAD, BWP, LSL, SZL."
          },
          "customerId": {
            "type": "string"
          },
          "customer": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "id"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  }
                }
              },
              {
                "type": "object",
                "required": [
                  "externalReference"
                ],
                "properties": {
                  "externalReference": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 190
                  }
                }
              }
            ]
          },
          "description": {
            "type": "string",
            "maxLength": 1000
          },
          "externalReference": {
            "type": "string",
            "minLength": 1,
            "maxLength": 190,
            "description": "Your ID. If an open order (awaiting_payment, awaiting_review or rejected) with this reference exists, it is returned with 200 instead of creating another; a different amount or currency is a 400."
          },
          "metadata": {
            "$ref": "#/components/schemas/MetadataInput"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "At least 5 minutes and at most 90 days ahead. Default: the gateway's expiry days (7 by default)."
          },
          "notifyPayer": {
            "type": "boolean",
            "default": false,
            "description": "Email the customer the bank details and `payUrl` when the order is created. Requires a customer with an email address (400 otherwise). Not re-sent when an existing open order is returned."
          }
        }
      },
      "ReconciliationTotals": {
        "type": "object",
        "required": [
          "receipts",
          "fees",
          "refunds",
          "payouts",
          "unpaids"
        ],
        "description": "Absolute sums of the statement lines in each category.",
        "properties": {
          "receipts": {
            "type": "number"
          },
          "fees": {
            "type": "number"
          },
          "refunds": {
            "type": "number"
          },
          "payouts": {
            "type": "number"
          },
          "unpaids": {
            "type": "number"
          }
        }
      },
      "ReconciliationRun": {
        "type": "object",
        "required": [
          "id",
          "source",
          "statementDate",
          "periodStart",
          "periodEnd",
          "status",
          "lineCount",
          "matched",
          "exceptions",
          "totals",
          "openingBalance",
          "closingBalance",
          "error",
          "createdAt",
          "completedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "netcash_statement",
              "csv_upload"
            ]
          },
          "statementDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "End of the statement period (or its start when no end is known)."
          },
          "periodStart": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "periodEnd": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "failed",
              "balanced",
              "exceptions"
            ],
            "description": "`balanced`: nothing needs attention; `exceptions`: at least one item does."
          },
          "lineCount": {
            "type": "integer",
            "description": "Statement lines imported."
          },
          "matched": {
            "type": "integer"
          },
          "exceptions": {
            "type": "integer",
            "description": "Items needing attention (mismatches, unmatched lines and not_received payments)."
          },
          "totals": {
            "$ref": "#/components/schemas/ReconciliationTotals"
          },
          "openingBalance": {
            "type": [
              "number",
              "null"
            ],
            "description": "From the statement's opening balance line, if present."
          },
          "closingBalance": {
            "type": [
              "number",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ReconciliationItem": {
        "type": "object",
        "required": [
          "status",
          "reference",
          "externalReference",
          "expectedAmount",
          "statementAmount",
          "date",
          "description",
          "note"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "matched",
              "amount_mismatch",
              "exception",
              "unmatched_receipt",
              "unmatched_debit",
              "not_received"
            ]
          },
          "reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "CentraPoint payment reference when matched, else the reference on the statement line."
          },
          "externalReference": {
            "type": [
              "string",
              "null"
            ],
            "description": "From the payment link or EFT order."
          },
          "expectedAmount": {
            "type": [
              "number",
              "null"
            ],
            "description": "The CentraPoint transaction amount."
          },
          "statementAmount": {
            "type": [
              "number",
              "null"
            ],
            "description": "Signed: + money in, − money out. Null for not_received."
          },
          "date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ReconciliationRunDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ReconciliationRun"
          },
          {
            "type": "object",
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ReconciliationItem"
                }
              }
            }
          }
        ]
      },
      "ReconciliationRunList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReconciliationRun"
            }
          }
        }
      },
      "EftWebhookEvent": {
        "type": "object",
        "required": [
          "id",
          "type",
          "created",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "eft.proof_received",
              "eft.rejected",
              "eft.expired",
              "eft.cancelled"
            ]
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "$ref": "#/components/schemas/EftOrder"
          }
        }
      },
      "ReconciliationWebhookEvent": {
        "type": "object",
        "required": [
          "id",
          "type",
          "created",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "reconciliation.completed"
            ]
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "type": "object",
            "required": [
              "runId",
              "status",
              "exceptions",
              "lineCount",
              "statementDate",
              "totals"
            ],
            "properties": {
              "runId": {
                "type": "string"
              },
              "status": {
                "type": "string",
                "enum": [
                  "processing",
                  "failed",
                  "balanced",
                  "exceptions"
                ]
              },
              "exceptions": {
                "type": "integer"
              },
              "lineCount": {
                "type": "integer"
              },
              "statementDate": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "totals": {
                "$ref": "#/components/schemas/ReconciliationTotals"
              }
            }
          }
        }
      },
      "SubscriptionWebhookData": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Subscription"
          },
          {
            "type": "object",
            "required": [
              "subscriptionId"
            ],
            "properties": {
              "subscriptionId": {
                "type": "string",
                "description": "Same as `id`."
              },
              "payment": {
                "type": "object",
                "description": "activated / charged / payment_failed only: the payment that triggered the event.",
                "required": [
                  "reference",
                  "amount",
                  "currency",
                  "status",
                  "paidAt"
                ],
                "properties": {
                  "reference": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "number"
                  },
                  "currency": {
                    "type": "string"
                  },
                  "status": {
                    "$ref": "#/components/schemas/TransactionStatus"
                  },
                  "paidAt": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                }
              },
              "periodStart": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "activated / charged / payment_failed only: when the paid period starts (the payment time). Null when the payment failed."
              },
              "periodEnd": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "periodStart plus one billing interval. Null when periodStart is null."
              }
            }
          }
        ]
      },
      "SubscriptionWebhookEvent": {
        "type": "object",
        "required": [
          "id",
          "type",
          "created",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "subscription.activated",
              "subscription.charged",
              "subscription.payment_failed",
              "subscription.cancelled",
              "subscription.paused",
              "subscription.resumed"
            ]
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "$ref": "#/components/schemas/SubscriptionWebhookData"
          }
        }
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Requests allowed per 60-second window for this API key.",
        "schema": {
          "type": "integer",
          "example": 120
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests left in the current window.",
        "schema": {
          "type": "integer",
          "example": 118
        }
      },
      "X-RateLimit-Reset": {
        "description": "Unix time (seconds) when the current window ends.",
        "schema": {
          "type": "integer",
          "example": 1790323260
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer",
          "example": 17
        }
      },
      "Idempotent-Replayed": {
        "description": "`true` when this response is a replay of an earlier request with the same Idempotency-Key.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Optional, up to 200 characters. Keys are scoped to your organisation, the HTTP method and the exact request path (e.g. `POST /api/v1/invoices/{id}/send` for one specific invoice). Retrying with the same key and the same body within 24 hours returns the stored response (with `Idempotent-Replayed: true`) instead of repeating the operation. Same key with a different body returns 409 `idempotency_conflict`. 5xx responses are not stored.",
        "schema": {
          "type": "string",
          "maxLength": 200
        },
        "example": "3f6c2a4e-8d1b-4f7a-9c2e-5b1d0a7e6f31"
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "1–100, default 20. Out-of-range values are clamped; invalid values fall back to 20.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 20
        }
      }
    }
  },
  "webhooks": {
    "payment.complete": {
      "post": {
        "operationId": "webhook_payment_complete",
        "summary": "Payment completed",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              },
              "example": {
                "id": "evt_3f9a1c7b2e4d6f8091a2b3c4",
                "type": "payment.complete",
                "created": "2026-09-25T08:14:04.201Z",
                "data": {
                  "reference": "CP-20260925-9F3A1C7B",
                  "status": "complete",
                  "amount": 499,
                  "currency": "ZAR",
                  "type": "once",
                  "gateway": "payfast",
                  "providerRef": "2458761",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "ORDER-1001",
                  "eftOrderId": null,
                  "invoice": null,
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001"
                  },
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "renewal": false,
                  "parentReference": null,
                  "subscriptionId": null,
                  "refundedAmount": 0,
                  "paidAt": "2026-09-25T08:14:03.512Z"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "payment.failed": {
      "post": {
        "operationId": "webhook_payment_failed",
        "summary": "Payment failed",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              },
              "example": {
                "id": "evt_3f9a1c7b2e4d6f8091a2b3c4",
                "type": "payment.failed",
                "created": "2026-09-25T08:14:04.201Z",
                "data": {
                  "reference": "CP-20260925-9F3A1C7B",
                  "status": "failed",
                  "amount": 499,
                  "currency": "ZAR",
                  "type": "once",
                  "gateway": "payfast",
                  "providerRef": "2458761",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "ORDER-1001",
                  "eftOrderId": null,
                  "invoice": null,
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001"
                  },
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "renewal": false,
                  "parentReference": null,
                  "subscriptionId": null,
                  "refundedAmount": 0,
                  "paidAt": null
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "payment.cancelled": {
      "post": {
        "operationId": "webhook_payment_cancelled",
        "summary": "Payment cancelled",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              },
              "example": {
                "id": "evt_3f9a1c7b2e4d6f8091a2b3c4",
                "type": "payment.cancelled",
                "created": "2026-09-25T08:14:04.201Z",
                "data": {
                  "reference": "CP-20260925-9F3A1C7B",
                  "status": "cancelled",
                  "amount": 499,
                  "currency": "ZAR",
                  "type": "once",
                  "gateway": "payfast",
                  "providerRef": "2458761",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "ORDER-1001",
                  "eftOrderId": null,
                  "invoice": null,
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001"
                  },
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "renewal": false,
                  "parentReference": null,
                  "subscriptionId": null,
                  "refundedAmount": 0,
                  "paidAt": null
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "payment.refunded": {
      "post": {
        "operationId": "webhook_payment_refunded",
        "summary": "Payment fully refunded",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              },
              "example": {
                "id": "evt_3f9a1c7b2e4d6f8091a2b3c4",
                "type": "payment.refunded",
                "created": "2026-09-25T08:14:04.201Z",
                "data": {
                  "reference": "CP-20260925-9F3A1C7B",
                  "status": "refunded",
                  "amount": 499,
                  "currency": "ZAR",
                  "type": "once",
                  "gateway": "payfast",
                  "providerRef": "2458761",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "ORDER-1001",
                  "eftOrderId": null,
                  "invoice": null,
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001"
                  },
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "renewal": false,
                  "parentReference": null,
                  "subscriptionId": null,
                  "refundedAmount": 499,
                  "paidAt": "2026-09-25T08:14:03.512Z"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "payment.partially_refunded": {
      "post": {
        "operationId": "webhook_payment_partially_refunded",
        "summary": "Payment partially refunded",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              },
              "example": {
                "id": "evt_3f9a1c7b2e4d6f8091a2b3c4",
                "type": "payment.partially_refunded",
                "created": "2026-09-25T08:14:04.201Z",
                "data": {
                  "reference": "CP-20260925-9F3A1C7B",
                  "status": "partially_refunded",
                  "amount": 499,
                  "currency": "ZAR",
                  "type": "once",
                  "gateway": "payfast",
                  "providerRef": "2458761",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "ORDER-1001",
                  "eftOrderId": null,
                  "invoice": null,
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001"
                  },
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "renewal": false,
                  "parentReference": null,
                  "subscriptionId": null,
                  "refundedAmount": 100,
                  "paidAt": "2026-09-25T08:14:03.512Z"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "invoice.paid": {
      "post": {
        "operationId": "webhook_invoice_paid",
        "summary": "Invoice fully paid",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceWebhookEvent"
              },
              "example": {
                "id": "evt_8b2d4f6a0c1e3a5b7d9f1c2e",
                "type": "invoice.paid",
                "created": "2026-10-02T08:15:01.000Z",
                "data": {
                  "id": "cmg2i9n0v0005inv0001abcd",
                  "number": "INV-000042",
                  "status": "paid",
                  "externalReference": "ORDER-1001",
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001",
                    "email": "thandi@example.co.za"
                  },
                  "currency": "ZAR",
                  "issueDate": "2026-09-24T22:00:00.000Z",
                  "dueDate": "2026-10-08T21:59:59.000Z",
                  "lines": [
                    {
                      "description": "Website hosting - October",
                      "quantity": 1,
                      "unitPrice": 434.78,
                      "taxRate": 15,
                      "amount": 434.78
                    },
                    {
                      "description": "Domain renewal (zero-rated)",
                      "quantity": 1,
                      "unitPrice": 120,
                      "taxRate": 0,
                      "amount": 120
                    }
                  ],
                  "subtotal": 554.78,
                  "taxAmount": 65.22,
                  "total": 620,
                  "amountPaid": 620,
                  "amountDue": 0,
                  "paidAt": "2026-10-02T08:15:00.000Z",
                  "payUrl": null,
                  "notes": "Payment due within 14 days.",
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "createdAt": "2026-09-25T08:05:00.000Z",
                  "updatedAt": "2026-09-25T08:05:00.000Z"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "invoice.partially_paid": {
      "post": {
        "operationId": "webhook_invoice_partially_paid",
        "summary": "Invoice partially paid",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceWebhookEvent"
              },
              "example": {
                "id": "evt_8b2d4f6a0c1e3a5b7d9f1c2e",
                "type": "invoice.partially_paid",
                "created": "2026-10-02T08:15:01.000Z",
                "data": {
                  "id": "cmg2i9n0v0005inv0001abcd",
                  "number": "INV-000042",
                  "status": "partially_paid",
                  "externalReference": "ORDER-1001",
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001",
                    "email": "thandi@example.co.za"
                  },
                  "currency": "ZAR",
                  "issueDate": "2026-09-24T22:00:00.000Z",
                  "dueDate": "2026-10-08T21:59:59.000Z",
                  "lines": [
                    {
                      "description": "Website hosting - October",
                      "quantity": 1,
                      "unitPrice": 434.78,
                      "taxRate": 15,
                      "amount": 434.78
                    },
                    {
                      "description": "Domain renewal (zero-rated)",
                      "quantity": 1,
                      "unitPrice": 120,
                      "taxRate": 0,
                      "amount": 120
                    }
                  ],
                  "subtotal": 554.78,
                  "taxAmount": 65.22,
                  "total": 620,
                  "amountPaid": 300,
                  "amountDue": 320,
                  "paidAt": null,
                  "payUrl": "https://app.centrapoint.co.za/pay/Xb7kP2qR9sLm4TvWz8Yc",
                  "notes": "Payment due within 14 days.",
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "createdAt": "2026-09-25T08:05:00.000Z",
                  "updatedAt": "2026-09-25T08:05:00.000Z"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "invoice.cancelled": {
      "post": {
        "operationId": "webhook_invoice_cancelled",
        "summary": "Invoice cancelled",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceWebhookEvent"
              },
              "example": {
                "id": "evt_8b2d4f6a0c1e3a5b7d9f1c2e",
                "type": "invoice.cancelled",
                "created": "2026-10-02T08:15:01.000Z",
                "data": {
                  "id": "cmg2i9n0v0005inv0001abcd",
                  "number": "INV-000042",
                  "status": "cancelled",
                  "externalReference": "ORDER-1001",
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001",
                    "email": "thandi@example.co.za"
                  },
                  "currency": "ZAR",
                  "issueDate": "2026-09-24T22:00:00.000Z",
                  "dueDate": "2026-10-08T21:59:59.000Z",
                  "lines": [
                    {
                      "description": "Website hosting - October",
                      "quantity": 1,
                      "unitPrice": 434.78,
                      "taxRate": 15,
                      "amount": 434.78
                    },
                    {
                      "description": "Domain renewal (zero-rated)",
                      "quantity": 1,
                      "unitPrice": 120,
                      "taxRate": 0,
                      "amount": 120
                    }
                  ],
                  "subtotal": 554.78,
                  "taxAmount": 65.22,
                  "total": 620,
                  "amountPaid": 0,
                  "amountDue": 620,
                  "paidAt": null,
                  "payUrl": null,
                  "notes": "Payment due within 14 days.",
                  "metadata": {
                    "crmId": "0061x00000AbCdE"
                  },
                  "createdAt": "2026-09-25T08:05:00.000Z",
                  "updatedAt": "2026-09-25T08:05:00.000Z"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "eft.proof_received": {
      "post": {
        "operationId": "webhook_eft_proof_received",
        "summary": "EFT proof of payment received",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EftWebhookEvent"
              },
              "example": {
                "id": "evt_5c7e9a1b3d5f7a9c1e3b5d7f",
                "type": "eft.proof_received",
                "created": "2026-09-25T09:12:01.000Z",
                "data": {
                  "id": "cmg3e1f2t0009eft0001abcd",
                  "reference": "EFT-7K3Q9P2M",
                  "status": "awaiting_review",
                  "amount": 1250,
                  "currency": "ZAR",
                  "description": "Order #1001",
                  "externalReference": "ORDER-1001",
                  "metadata": {
                    "cartId": "c_8841"
                  },
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001"
                  },
                  "bankDetails": {
                    "bankName": "FNB",
                    "accountName": "Example Traders (Pty) Ltd",
                    "accountNumber": "62000000000",
                    "branchCode": "250655",
                    "accountType": "current"
                  },
                  "paymentReference": "EFT-7K3Q9P2M",
                  "payUrl": "https://app.centrapoint.co.za/eft/cmg3e1f2t0009eft0001abcd.Qm9ndXMtc2lnbmF0dXJlLWV4YW1wbGU",
                  "proofs": [
                    {
                      "id": "cmg3p0r0f0011prf0001abcd",
                      "fileName": "proof.pdf",
                      "contentType": "application/pdf",
                      "size": 184213,
                      "source": "payer",
                      "createdAt": "2026-09-25T09:12:00.000Z"
                    }
                  ],
                  "rejectionReason": null,
                  "expiresAt": "2026-10-02T08:00:00.000Z",
                  "reviewedAt": null,
                  "paidAt": null,
                  "createdAt": "2026-09-25T08:00:00.000Z"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "eft.rejected": {
      "post": {
        "operationId": "webhook_eft_rejected",
        "summary": "EFT proof rejected",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EftWebhookEvent"
              },
              "example": {
                "id": "evt_5c7e9a1b3d5f7a9c1e3b5d7f",
                "type": "eft.rejected",
                "created": "2026-09-25T09:12:01.000Z",
                "data": {
                  "id": "cmg3e1f2t0009eft0001abcd",
                  "reference": "EFT-7K3Q9P2M",
                  "status": "rejected",
                  "amount": 1250,
                  "currency": "ZAR",
                  "description": "Order #1001",
                  "externalReference": "ORDER-1001",
                  "metadata": {
                    "cartId": "c_8841"
                  },
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001"
                  },
                  "bankDetails": {
                    "bankName": "FNB",
                    "accountName": "Example Traders (Pty) Ltd",
                    "accountNumber": "62000000000",
                    "branchCode": "250655",
                    "accountType": "current"
                  },
                  "paymentReference": "EFT-7K3Q9P2M",
                  "payUrl": "https://app.centrapoint.co.za/eft/cmg3e1f2t0009eft0001abcd.Qm9ndXMtc2lnbmF0dXJlLWV4YW1wbGU",
                  "proofs": [
                    {
                      "id": "cmg3p0r0f0011prf0001abcd",
                      "fileName": "proof.pdf",
                      "contentType": "application/pdf",
                      "size": 184213,
                      "source": "payer",
                      "createdAt": "2026-09-25T09:12:00.000Z"
                    }
                  ],
                  "rejectionReason": "Deposit not received",
                  "expiresAt": "2026-10-02T08:00:00.000Z",
                  "reviewedAt": "2026-09-25T11:00:00.000Z",
                  "paidAt": null,
                  "createdAt": "2026-09-25T08:00:00.000Z"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "eft.expired": {
      "post": {
        "operationId": "webhook_eft_expired",
        "summary": "EFT order expired",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EftWebhookEvent"
              },
              "example": {
                "id": "evt_5c7e9a1b3d5f7a9c1e3b5d7f",
                "type": "eft.expired",
                "created": "2026-09-25T09:12:01.000Z",
                "data": {
                  "id": "cmg3e1f2t0009eft0001abcd",
                  "reference": "EFT-7K3Q9P2M",
                  "status": "expired",
                  "amount": 1250,
                  "currency": "ZAR",
                  "description": "Order #1001",
                  "externalReference": "ORDER-1001",
                  "metadata": {
                    "cartId": "c_8841"
                  },
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001"
                  },
                  "bankDetails": {
                    "bankName": "FNB",
                    "accountName": "Example Traders (Pty) Ltd",
                    "accountNumber": "62000000000",
                    "branchCode": "250655",
                    "accountType": "current"
                  },
                  "paymentReference": "EFT-7K3Q9P2M",
                  "payUrl": "https://app.centrapoint.co.za/eft/cmg3e1f2t0009eft0001abcd.Qm9ndXMtc2lnbmF0dXJlLWV4YW1wbGU",
                  "proofs": [],
                  "rejectionReason": null,
                  "expiresAt": "2026-10-02T08:00:00.000Z",
                  "reviewedAt": null,
                  "paidAt": null,
                  "createdAt": "2026-09-25T08:00:00.000Z"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "eft.cancelled": {
      "post": {
        "operationId": "webhook_eft_cancelled",
        "summary": "EFT order cancelled",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EftWebhookEvent"
              },
              "example": {
                "id": "evt_5c7e9a1b3d5f7a9c1e3b5d7f",
                "type": "eft.cancelled",
                "created": "2026-09-25T09:12:01.000Z",
                "data": {
                  "id": "cmg3e1f2t0009eft0001abcd",
                  "reference": "EFT-7K3Q9P2M",
                  "status": "cancelled",
                  "amount": 1250,
                  "currency": "ZAR",
                  "description": "Order #1001",
                  "externalReference": "ORDER-1001",
                  "metadata": {
                    "cartId": "c_8841"
                  },
                  "customer": {
                    "id": "cmg2c0s7t0003cust0001abcd",
                    "externalReference": "CRM-1001"
                  },
                  "bankDetails": {
                    "bankName": "FNB",
                    "accountName": "Example Traders (Pty) Ltd",
                    "accountNumber": "62000000000",
                    "branchCode": "250655",
                    "accountType": "current"
                  },
                  "paymentReference": "EFT-7K3Q9P2M",
                  "payUrl": "https://app.centrapoint.co.za/eft/cmg3e1f2t0009eft0001abcd.Qm9ndXMtc2lnbmF0dXJlLWV4YW1wbGU",
                  "proofs": [],
                  "rejectionReason": null,
                  "expiresAt": "2026-10-02T08:00:00.000Z",
                  "reviewedAt": null,
                  "paidAt": null,
                  "createdAt": "2026-09-25T08:00:00.000Z"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "reconciliation.completed": {
      "post": {
        "operationId": "webhook_reconciliation_completed",
        "summary": "Reconciliation run completed",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReconciliationWebhookEvent"
              },
              "example": {
                "id": "evt_5c7e9a1b3d5f7a9c1e3b5d7f",
                "type": "reconciliation.completed",
                "created": "2026-09-25T09:12:01.000Z",
                "data": {
                  "runId": "cmg3r7c0n0013run0001abcd",
                  "status": "exceptions",
                  "exceptions": 2,
                  "lineCount": 42,
                  "statementDate": "2026-09-24T22:00:00.000Z",
                  "totals": {
                    "receipts": 48250.5,
                    "fees": 612.4,
                    "refunds": 499,
                    "payouts": 45000,
                    "unpaids": 350
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "subscription.activated": {
      "post": {
        "operationId": "webhook_subscription_activated",
        "summary": "Subscription activated (first payment completed)",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscriptionWebhookEvent"
              },
              "example": {
                "id": "evt_2a4c6e8f0b1d3f5a7c9e1b3d",
                "type": "subscription.activated",
                "created": "2026-10-25T08:14:05.000Z",
                "data": {
                  "id": "cmg2s5b8k0007sub0001abcd",
                  "status": "active",
                  "frequency": "monthly",
                  "amount": 499,
                  "currency": "ZAR",
                  "gateway": "payfast",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "SUB-ACME-PRO",
                  "metadata": {
                    "plan": "pro"
                  },
                  "createdAt": "2026-09-25T08:14:04.000Z",
                  "updatedAt": "2026-09-25T08:14:04.000Z",
                  "subscriptionId": "cmg2s5b8k0007sub0001abcd",
                  "payment": {
                    "reference": "CP-20260925-9F3A1C7B",
                    "amount": 499,
                    "currency": "ZAR",
                    "status": "complete",
                    "paidAt": "2026-09-25T08:14:03.512Z"
                  },
                  "periodStart": "2026-09-25T08:14:03.512Z",
                  "periodEnd": "2026-10-25T08:14:03.512Z"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "subscription.charged": {
      "post": {
        "operationId": "webhook_subscription_charged",
        "summary": "Subscription renewal charged",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscriptionWebhookEvent"
              },
              "example": {
                "id": "evt_2a4c6e8f0b1d3f5a7c9e1b3d",
                "type": "subscription.charged",
                "created": "2026-10-25T08:14:05.000Z",
                "data": {
                  "id": "cmg2s5b8k0007sub0001abcd",
                  "status": "active",
                  "frequency": "monthly",
                  "amount": 499,
                  "currency": "ZAR",
                  "gateway": "payfast",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "SUB-ACME-PRO",
                  "metadata": {
                    "plan": "pro"
                  },
                  "createdAt": "2026-09-25T08:14:04.000Z",
                  "updatedAt": "2026-09-25T08:14:04.000Z",
                  "subscriptionId": "cmg2s5b8k0007sub0001abcd",
                  "payment": {
                    "reference": "CP-20260925-9F3A1C7B-2458799",
                    "amount": 499,
                    "currency": "ZAR",
                    "status": "complete",
                    "paidAt": "2026-10-25T08:14:03.000Z"
                  },
                  "periodStart": "2026-10-25T08:14:03.000Z",
                  "periodEnd": "2026-11-25T08:14:03.000Z"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "subscription.payment_failed": {
      "post": {
        "operationId": "webhook_subscription_payment_failed",
        "summary": "Subscription renewal failed",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscriptionWebhookEvent"
              },
              "example": {
                "id": "evt_2a4c6e8f0b1d3f5a7c9e1b3d",
                "type": "subscription.payment_failed",
                "created": "2026-10-25T08:14:05.000Z",
                "data": {
                  "id": "cmg2s5b8k0007sub0001abcd",
                  "status": "active",
                  "frequency": "monthly",
                  "amount": 499,
                  "currency": "ZAR",
                  "gateway": "payfast",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "SUB-ACME-PRO",
                  "metadata": {
                    "plan": "pro"
                  },
                  "createdAt": "2026-09-25T08:14:04.000Z",
                  "updatedAt": "2026-09-25T08:14:04.000Z",
                  "subscriptionId": "cmg2s5b8k0007sub0001abcd",
                  "payment": {
                    "reference": "CP-20260925-9F3A1C7B-2458912",
                    "amount": 499,
                    "currency": "ZAR",
                    "status": "failed",
                    "paidAt": null
                  },
                  "periodStart": null,
                  "periodEnd": null
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "subscription.cancelled": {
      "post": {
        "operationId": "webhook_subscription_cancelled",
        "summary": "Subscription cancelled",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscriptionWebhookEvent"
              },
              "example": {
                "id": "evt_2a4c6e8f0b1d3f5a7c9e1b3d",
                "type": "subscription.cancelled",
                "created": "2026-10-25T08:14:05.000Z",
                "data": {
                  "id": "cmg2s5b8k0007sub0001abcd",
                  "status": "cancelled",
                  "frequency": "monthly",
                  "amount": 499,
                  "currency": "ZAR",
                  "gateway": "payfast",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "SUB-ACME-PRO",
                  "metadata": {
                    "plan": "pro"
                  },
                  "createdAt": "2026-09-25T08:14:04.000Z",
                  "updatedAt": "2026-09-25T08:14:04.000Z",
                  "subscriptionId": "cmg2s5b8k0007sub0001abcd"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "subscription.paused": {
      "post": {
        "operationId": "webhook_subscription_paused",
        "summary": "Subscription paused",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscriptionWebhookEvent"
              },
              "example": {
                "id": "evt_2a4c6e8f0b1d3f5a7c9e1b3d",
                "type": "subscription.paused",
                "created": "2026-10-25T08:14:05.000Z",
                "data": {
                  "id": "cmg2s5b8k0007sub0001abcd",
                  "status": "paused",
                  "frequency": "monthly",
                  "amount": 499,
                  "currency": "ZAR",
                  "gateway": "payfast",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "SUB-ACME-PRO",
                  "metadata": {
                    "plan": "pro"
                  },
                  "createdAt": "2026-09-25T08:14:04.000Z",
                  "updatedAt": "2026-09-25T08:14:04.000Z",
                  "subscriptionId": "cmg2s5b8k0007sub0001abcd"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    },
    "subscription.resumed": {
      "post": {
        "operationId": "webhook_subscription_resumed",
        "summary": "Subscription resumed",
        "description": "Sent by CentraPoint to each HTTPS endpoint configured under Settings → Webhooks and subscribed to this event. Respond 2xx within 10 seconds. Failed deliveries are retried after 1m, 5m, 30m, 2h, 6h, 12h and 24h (8 attempts). Redirects are not followed. Deliveries may repeat and may arrive out of order.",
        "parameters": [
          {
            "name": "CentraPoint-Event",
            "in": "header",
            "required": true,
            "description": "Event type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Delivery",
            "in": "header",
            "required": true,
            "description": "Delivery ID (constant across retries of this delivery).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "CentraPoint-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256(signing secret, \"<t>.<raw body>\")>`. Reject if `t` is more than 5 minutes old.",
            "schema": {
              "type": "string"
            },
            "example": "t=1790323244,v1=5d1c0e8f0b6f4a3c9e2d7b1a0c9f8e7d6c5b4a3928171605f4e3d2c1b0a99887"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscriptionWebhookEvent"
              },
              "example": {
                "id": "evt_2a4c6e8f0b1d3f5a7c9e1b3d",
                "type": "subscription.resumed",
                "created": "2026-10-25T08:14:05.000Z",
                "data": {
                  "id": "cmg2s5b8k0007sub0001abcd",
                  "status": "active",
                  "frequency": "monthly",
                  "amount": 499,
                  "currency": "ZAR",
                  "gateway": "payfast",
                  "paymentLinkId": "cmg1k2x3y0001abcd1234efgh",
                  "externalReference": "SUB-ACME-PRO",
                  "metadata": {
                    "plan": "pro"
                  },
                  "createdAt": "2026-09-25T08:14:04.000Z",
                  "updatedAt": "2026-09-25T08:14:04.000Z",
                  "subscriptionId": "cmg2s5b8k0007sub0001abcd"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other response (or a timeout) is retried."
          }
        }
      }
    }
  }
}
