{
  "openapi": "3.1.0",
  "info": {
    "title": "Sparelane Sandbox Merchant API",
    "version": "2026-10-23.phase-6c",
    "summary": "Merchant-facing Sparelane sandbox API contract.",
    "description": "External sandbox contract for merchant integrations. All payments, payment methods and outcomes are fictional. Consumer consent is completed outside merchant API credentials."
  },
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "servers": [
    {
      "url": "https://api.sandbox.sparelane.com.au/v1",
      "description": "Future external sandbox merchant API hostname. Not active until DNS and dedicated deployment are approved."
    }
  ],
  "security": [
    {
      "merchantBearer": []
    }
  ],
  "tags": [
    { "name": "Merchant" },
    { "name": "Customers" },
    { "name": "Link Authorisations" },
    { "name": "Mandates" },
    { "name": "Bills" },
    { "name": "Payments" },
    { "name": "Webhooks" },
    { "name": "Sandbox Simulations" }
  ],
  "paths": {
    "/merchant": {
      "get": {
        "operationId": "getMerchantProfile",
        "tags": ["Merchant"],
        "summary": "Get authenticated merchant profile",
        "description": "Returns the merchant resolved from the bearer API key. Merchant identity is never accepted from request parameters.",
        "responses": {
          "200": { "$ref": "#/components/responses/MerchantProfileResponse" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenMerchant" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/RateLimitUnavailable" }
        }
      }
    },
    "/customers": {
      "get": {
        "operationId": "listCustomers",
        "tags": ["Customers"],
        "summary": "List merchant customers",
        "description": "Lists only customers belonging to the authenticated merchant.",
        "parameters": [{ "$ref": "#/components/parameters/Limit" }],
        "responses": {
          "200": { "$ref": "#/components/responses/CustomersResponse" },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "operationId": "createCustomer",
        "tags": ["Customers"],
        "summary": "Create a merchant customer",
        "description": "Creates a merchant-scoped customer with an opaque placeholder consumer. The global consumer ID is not exposed. externalCustomerId is unique per merchant.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateCustomerRequest" },
              "examples": {
                "create": {
                  "value": {
                    "externalCustomerId": "cust_demo_001",
                    "displayName": "Sarah Example",
                    "email": "sarah.customer@example.test"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "$ref": "#/components/responses/CustomerResponse" },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/customers/{customerId}": {
      "parameters": [{ "$ref": "#/components/parameters/CustomerId" }],
      "get": {
        "operationId": "getCustomer",
        "tags": ["Customers"],
        "summary": "Get a merchant customer",
        "description": "Returns 404 for customers outside the authenticated merchant tenant.",
        "responses": {
          "200": { "$ref": "#/components/responses/CustomerResponse" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "patch": {
        "operationId": "updateCustomer",
        "tags": ["Customers"],
        "summary": "Update merchant-owned customer fields",
        "description": "Allows updates to merchant-owned display fields only. Consumer identity, merchant ownership, mandates and authorisation state cannot be modified.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/UpdateCustomerRequest" },
              "examples": {
                "update": {
                  "value": {
                    "displayName": "Sarah Customer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/CustomerResponse" },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/customers/{customerId}/link-authorizations": {
      "parameters": [{ "$ref": "#/components/parameters/CustomerId" }],
      "post": {
        "operationId": "createCustomerLinkAuthorization",
        "tags": ["Link Authorisations"],
        "summary": "Create a consumer link authorisation",
        "description": "Creates a short-lived, single-use invitation for this merchant customer. The invitation token is returned once. Consumer approval requires separate consumer authentication outside merchant API credentials.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateLinkAuthorizationRequest" },
              "examples": {
                "default": {
                  "value": {
                    "expiresInMinutes": 30
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "$ref": "#/components/responses/LinkAuthorizationCreatedResponse" },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/customers/{customerId}/link-authorizations/{authorizationId}/cancel": {
      "parameters": [
        { "$ref": "#/components/parameters/CustomerId" },
        { "$ref": "#/components/parameters/AuthorizationId" }
      ],
      "post": {
        "operationId": "cancelCustomerLinkAuthorization",
        "tags": ["Link Authorisations"],
        "summary": "Cancel a pending link authorisation",
        "description": "Cancels a pending link authorisation owned by this merchant customer. Already non-pending authorisations are returned unchanged.",
        "responses": {
          "200": { "$ref": "#/components/responses/LinkAuthorizationResponse" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/mandates": {
      "get": {
        "operationId": "listMandates",
        "tags": ["Mandates"],
        "summary": "List merchant payment mandates",
        "description": "Lists mandates owned by the authenticated merchant. Payment method details are masked fictional metadata only.",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/MandateStatus" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/MandatesResponse" },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/bills": {
      "get": {
        "operationId": "listBills",
        "tags": ["Bills"],
        "summary": "List bills",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/BillStatus" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/BillsResponse" },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "operationId": "submitBill",
        "tags": ["Bills"],
        "summary": "Submit a fictional bill",
        "description": "Creates an issued bill for a merchant customer identified by externalCustomerId. idempotencyKey is required and maps to externalBillId when externalBillId is omitted. Matching repeated submissions return 200 with idempotent=true; conflicting payloads return 409.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SubmitBillRequest" },
              "examples": {
                "bill": {
                  "value": {
                    "externalCustomerId": "cust_demo_001",
                    "amountCents": 4995,
                    "currency": "AUD",
                    "dueAt": "2026-11-15T00:00:00.000Z",
                    "description": "Fictional monthly subscription",
                    "idempotencyKey": "bill-demo-2026-11-001"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/BillSubmittedResponse" },
          "201": { "$ref": "#/components/responses/BillSubmittedResponse" },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/bills/{billId}": {
      "parameters": [{ "$ref": "#/components/parameters/BillId" }],
      "get": {
        "operationId": "getBill",
        "tags": ["Bills"],
        "summary": "Get a bill",
        "description": "Returns 404 for bills outside the authenticated merchant tenant.",
        "responses": {
          "200": { "$ref": "#/components/responses/BillResponse" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/bills/{billId}/simulations": {
      "parameters": [{ "$ref": "#/components/parameters/BillId" }],
      "post": {
        "operationId": "simulateBillPayment",
        "tags": ["Sandbox Simulations"],
        "summary": "Run a sandbox payment simulation",
        "description": "Sandbox-only deterministic fictional payment simulation for a merchant-owned bill. Does not move real money, change consumer consent or call an external payment provider.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SimulatePaymentRequest" },
              "examples": {
                "primaryDeclineBackupSuccess": {
                  "value": {
                    "scenario": "PRIMARY_DECLINE_BACKUP_SUCCESS"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/SimulationResponse" },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/payments": {
      "get": {
        "operationId": "listPayments",
        "tags": ["Payments"],
        "summary": "List payments",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/PaymentStatus" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/PaymentsResponse" },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/payments/{paymentId}": {
      "parameters": [{ "$ref": "#/components/parameters/PaymentId" }],
      "get": {
        "operationId": "getPayment",
        "tags": ["Payments"],
        "summary": "Get a payment",
        "description": "Returns 404 for payments outside the authenticated merchant tenant.",
        "responses": {
          "200": { "$ref": "#/components/responses/PaymentResponse" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/webhooks": {
      "get": {
        "operationId": "listWebhookEndpoints",
        "tags": ["Webhooks"],
        "summary": "List webhook endpoints",
        "responses": {
          "200": { "$ref": "#/components/responses/WebhookEndpointsResponse" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "operationId": "createWebhookEndpoint",
        "tags": ["Webhooks"],
        "summary": "Create webhook endpoint",
        "description": "Registers an HTTPS webhook endpoint. Localhost, private, internal, link-local and metadata destinations are rejected. The plaintext signing secret is returned once and only its SHA-256 hash is stored.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateWebhookEndpointRequest" },
              "examples": {
                "endpoint": {
                  "value": {
                    "url": "https://webhooks.example.test/sparelane",
                    "description": "Sandbox webhook endpoint",
                    "eventTypes": ["payment.succeeded", "payment.failed", "payment.recovered"]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "$ref": "#/components/responses/WebhookEndpointCreatedResponse" },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/webhooks/{endpointId}/deliveries": {
      "parameters": [{ "$ref": "#/components/parameters/WebhookEndpointId" }],
      "get": {
        "operationId": "listWebhookDeliveries",
        "tags": ["Webhooks"],
        "summary": "List webhook delivery history",
        "description": "Returns the latest delivery records for an endpoint owned by the authenticated merchant.",
        "responses": {
          "200": { "$ref": "#/components/responses/WebhookDeliveriesResponse" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/webhooks/{endpointId}/disable": {
      "parameters": [{ "$ref": "#/components/parameters/WebhookEndpointId" }],
      "post": {
        "operationId": "disableWebhookEndpoint",
        "tags": ["Webhooks"],
        "summary": "Disable webhook endpoint",
        "responses": {
          "200": { "$ref": "#/components/responses/WebhookEndpointResponse" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/webhooks/{endpointId}/rotate-secret": {
      "parameters": [{ "$ref": "#/components/parameters/WebhookEndpointId" }],
      "post": {
        "operationId": "rotateWebhookSigningSecret",
        "tags": ["Webhooks"],
        "summary": "Rotate webhook signing secret",
        "description": "Generates and returns a replacement signing secret once. Existing delivery signing uses the current stored secret hash.",
        "responses": {
          "200": { "$ref": "#/components/responses/WebhookSecretRotatedResponse" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/webhooks/{endpointId}/test-event": {
      "parameters": [{ "$ref": "#/components/parameters/WebhookEndpointId" }],
      "post": {
        "operationId": "generateWebhookTestEvent",
        "tags": ["Webhooks"],
        "summary": "Generate signed webhook test payload",
        "description": "Returns a signed test payload and headers without delivering it.",
        "responses": {
          "200": { "$ref": "#/components/responses/WebhookTestEventResponse" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "merchantBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Sparelane sandbox merchant API key"
      }
    },
    "parameters": {
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 },
        "description": "Maximum records to return. Cursor pagination is not yet implemented."
      },
      "CustomerId": { "name": "customerId", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1 } },
      "AuthorizationId": { "name": "authorizationId", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1 } },
      "BillId": { "name": "billId", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1 } },
      "PaymentId": { "name": "paymentId", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1 } },
      "WebhookEndpointId": { "name": "endpointId", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1 } },
      "MandateStatus": {
        "name": "status",
        "in": "query",
        "schema": { "type": "string", "enum": ["PENDING", "ACTIVE", "PAUSED", "CANCELLED", "EXPIRED"] }
      },
      "BillStatus": {
        "name": "status",
        "in": "query",
        "schema": { "type": "string", "enum": ["DRAFT", "ISSUED", "SCHEDULED", "PARTIALLY_PAID", "PAID", "OVERDUE", "CANCELLED"] }
      },
      "PaymentStatus": {
        "name": "status",
        "in": "query",
        "schema": { "type": "string", "enum": ["SCHEDULED", "PROCESSING", "SUCCEEDED", "FAILED", "CANCELLED", "REFUNDED", "DISPUTED"] }
      }
    },
    "headers": {
      "RequestId": {
        "schema": { "type": "string" },
        "description": "Correlation ID. Supply `x-request-id` or use the generated value returned by the API."
      }
    },
    "schemas": {
      "ApiError": {
        "type": "object",
        "required": ["error"],
        "additionalProperties": false,
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "additionalProperties": false,
            "properties": {
              "code": {
                "type": "string",
                "enum": ["authentication_required", "invalid_api_key", "api_key_expired", "api_key_revoked", "merchant_inactive", "invalid_request", "not_found", "conflict", "rate_limit_exceeded", "internal_error"]
              },
              "message": { "type": "string" }
            }
          }
        }
      },
      "Merchant": {
        "type": "object",
        "required": ["id", "slug", "displayName", "status", "defaultCurrency"],
        "properties": {
          "id": { "type": "string" },
          "slug": { "type": "string" },
          "displayName": { "type": "string" },
          "status": { "type": "string" },
          "defaultCurrency": { "type": "string", "const": "AUD" },
          "supportEmail": { "type": ["string", "null"], "format": "email" }
        }
      },
      "MerchantApiKeySummary": {
        "type": "object",
        "required": ["id", "name", "keyPrefix"],
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "keyPrefix": { "type": "string" }
        }
      },
      "Customer": {
        "type": "object",
        "required": ["id", "externalCustomerId", "displayName", "email", "status", "createdAt", "updatedAt"],
        "properties": {
          "id": { "type": "string" },
          "externalCustomerId": { "type": "string" },
          "displayName": { "type": "string" },
          "email": { "type": "string", "format": "email" },
          "status": { "type": "string" },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" },
          "consumerLink": {
            "type": "object",
            "properties": {
              "status": { "type": "string" },
              "authorizationRequired": { "type": "boolean" }
            }
          }
        }
      },
      "CreateCustomerRequest": {
        "type": "object",
        "required": ["externalCustomerId", "displayName", "email"],
        "additionalProperties": false,
        "properties": {
          "externalCustomerId": { "type": "string", "minLength": 1, "maxLength": 120 },
          "displayName": { "type": "string", "minLength": 1, "maxLength": 120 },
          "email": { "type": "string", "format": "email", "maxLength": 254 }
        }
      },
      "UpdateCustomerRequest": {
        "type": "object",
        "minProperties": 1,
        "additionalProperties": false,
        "properties": {
          "externalCustomerId": { "type": "string", "minLength": 1, "maxLength": 120 },
          "displayName": { "type": "string", "minLength": 1, "maxLength": 120 },
          "email": { "type": "string", "format": "email", "maxLength": 254 }
        }
      },
      "LinkAuthorization": {
        "type": "object",
        "required": ["id", "status", "expiresAt", "consentText", "consentVersion", "merchant", "customer"],
        "properties": {
          "id": { "type": "string" },
          "status": { "type": "string", "enum": ["PENDING", "CONSUMED", "EXPIRED", "CANCELLED"] },
          "expiresAt": { "type": "string", "format": "date-time" },
          "consentText": { "type": "string" },
          "consentVersion": { "type": "string" },
          "merchant": {
            "type": "object",
            "required": ["slug", "displayName"],
            "properties": {
              "slug": { "type": "string" },
              "displayName": { "type": "string" }
            }
          },
          "customer": { "$ref": "#/components/schemas/LinkAuthorizationCustomer" }
        }
      },
      "LinkAuthorizationCustomer": {
        "type": "object",
        "required": ["id", "externalCustomerId", "displayName", "email", "status"],
        "properties": {
          "id": { "type": "string" },
          "externalCustomerId": { "type": "string" },
          "displayName": { "type": "string" },
          "email": { "type": "string", "format": "email" },
          "status": { "type": "string" }
        }
      },
      "CreateLinkAuthorizationRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "expiresInMinutes": { "type": "integer", "minimum": 5, "maximum": 1440 },
          "consentText": { "type": "string", "minLength": 20, "maxLength": 2000 },
          "consentVersion": { "type": "string", "minLength": 3, "maxLength": 80 }
        }
      },
      "PaymentMethod": {
        "type": "object",
        "required": ["id", "type", "status", "maskedLabel"],
        "properties": {
          "id": { "type": "string" },
          "type": { "type": "string", "enum": ["CARD", "BANK_ACCOUNT"] },
          "status": { "type": "string" },
          "maskedLabel": { "type": "string" },
          "cardNetwork": { "type": ["string", "null"] },
          "cardLast4": { "type": ["string", "null"], "pattern": "^[0-9]{4}$" },
          "bankInstitutionName": { "type": ["string", "null"] },
          "bankAccountLast4": { "type": ["string", "null"], "pattern": "^[0-9]{4}$" },
          "scenarioCode": { "type": ["string", "null"] }
        }
      },
      "Mandate": {
        "type": "object",
        "required": ["id", "reference", "status", "currency", "createdAt", "updatedAt", "merchantCustomer"],
        "properties": {
          "id": { "type": "string" },
          "reference": { "type": "string" },
          "status": { "type": "string" },
          "currency": { "type": "string" },
          "maxAmountCents": { "type": ["integer", "null"] },
          "startsAt": { "type": "string", "format": "date-time" },
          "endsAt": { "type": ["string", "null"], "format": "date-time" },
          "consentAcceptedAt": { "type": ["string", "null"], "format": "date-time" },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" },
          "merchantCustomer": { "$ref": "#/components/schemas/CustomerSummary" },
          "paymentMethods": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": { "type": "string" },
                "priority": { "type": "integer" },
                "status": { "type": "string" },
                "paymentMethod": { "$ref": "#/components/schemas/PaymentMethod" }
              }
            }
          }
        }
      },
      "CustomerSummary": {
        "type": "object",
        "required": ["id", "externalCustomerId", "displayName"],
        "properties": {
          "id": { "type": "string" },
          "externalCustomerId": { "type": "string" },
          "displayName": { "type": "string" }
        }
      },
      "SubmitBillRequest": {
        "type": "object",
        "required": ["externalCustomerId", "amountCents", "currency", "dueAt", "description", "idempotencyKey"],
        "additionalProperties": false,
        "properties": {
          "externalCustomerId": { "type": "string", "minLength": 1, "maxLength": 120 },
          "amountCents": { "type": "integer", "exclusiveMinimum": 0, "maximum": 10000000 },
          "currency": { "type": "string", "enum": ["AUD"], "default": "AUD" },
          "dueAt": { "type": "string", "format": "date-time" },
          "description": { "type": "string", "minLength": 1, "maxLength": 500 },
          "idempotencyKey": { "type": "string", "minLength": 8, "maxLength": 120 },
          "externalBillId": { "type": "string", "minLength": 1, "maxLength": 120 }
        }
      },
      "Bill": {
        "type": "object",
        "required": ["id", "externalBillId", "status", "description", "amountCents", "currency", "dueAt", "createdAt", "updatedAt"],
        "properties": {
          "id": { "type": "string" },
          "externalBillId": { "type": "string" },
          "status": { "type": "string" },
          "description": { "type": "string" },
          "amountCents": { "type": "integer" },
          "currency": { "type": "string" },
          "issuedAt": { "type": ["string", "null"], "format": "date-time" },
          "dueAt": { "type": "string", "format": "date-time" },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" },
          "customer": { "$ref": "#/components/schemas/CustomerSummary" },
          "mandate": {
            "oneOf": [
              { "type": "null" },
              {
                "type": "object",
                "properties": {
                  "id": { "type": "string" },
                  "reference": { "type": "string" },
                  "status": { "type": "string" },
                  "paymentSelectionMode": { "type": "string", "enum": ["FOLLOW_WALLET_DEFAULTS", "CUSTOM"] }
                }
              }
            ]
          },
          "payments": { "type": "array", "items": { "$ref": "#/components/schemas/PaymentSummary" } }
        }
      },
      "PaymentSummary": {
        "type": "object",
        "required": ["id", "status", "amountCents", "currency", "scheduledAt", "attemptCount"],
        "properties": {
          "id": { "type": "string" },
          "status": { "type": "string" },
          "amountCents": { "type": "integer" },
          "currency": { "type": "string" },
          "scheduledAt": { "type": "string", "format": "date-time" },
          "processedAt": { "type": ["string", "null"], "format": "date-time" },
          "attemptCount": { "type": "integer" },
          "finalOutcome": { "type": ["string", "null"] },
          "finalPaymentMethod": { "oneOf": [{ "type": "null" }, { "$ref": "#/components/schemas/PaymentMethod" }] },
          "customer": { "$ref": "#/components/schemas/CustomerSummary" }
        }
      },
      "Payment": {
        "allOf": [
          { "$ref": "#/components/schemas/PaymentSummary" },
          {
            "type": "object",
            "properties": {
              "bill": {
                "oneOf": [
                  { "type": "null" },
                  {
                    "type": "object",
                    "properties": {
                      "id": { "type": "string" },
                      "externalBillId": { "type": "string" },
                      "status": { "type": "string" },
                      "description": { "type": "string" },
                      "amountCents": { "type": "integer" },
                      "currency": { "type": "string" },
                      "dueAt": { "type": "string", "format": "date-time" }
                    }
                  }
                ]
              },
              "attempts": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string" },
                    "attemptNumber": { "type": "integer" },
                    "status": { "type": "string" },
                    "outcome": { "type": ["string", "null"] },
                    "amountCents": { "type": "integer" },
                    "currency": { "type": "string" },
                    "failureCode": { "type": ["string", "null"] },
                    "failureMessage": { "type": ["string", "null"] },
                    "attemptedAt": { "type": "string", "format": "date-time" },
                    "completedAt": { "type": ["string", "null"], "format": "date-time" },
                    "paymentMethod": { "oneOf": [{ "type": "null" }, { "$ref": "#/components/schemas/PaymentMethod" }] }
                  }
                }
              }
            }
          }
        ]
      },
      "SimulatePaymentRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "scenario": {
            "type": "string",
            "enum": ["PRIMARY_SUCCESS", "PRIMARY_DECLINE_BACKUP_SUCCESS", "ALL_METHODS_DECLINE", "NO_ELIGIBLE_METHOD", "MANDATE_REVOKED"],
            "default": "PRIMARY_SUCCESS"
          }
        }
      },
      "WebhookEndpoint": {
        "type": "object",
        "required": ["id", "url", "status", "eventTypes", "createdAt", "updatedAt"],
        "properties": {
          "id": { "type": "string" },
          "url": { "type": "string", "format": "uri" },
          "description": { "type": ["string", "null"] },
          "status": { "type": "string" },
          "eventTypes": {
            "type": "array",
            "items": { "type": "string", "enum": ["payment.succeeded", "payment.failed", "payment.recovered"] }
          },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "CreateWebhookEndpointRequest": {
        "type": "object",
        "required": ["url", "eventTypes"],
        "additionalProperties": false,
        "properties": {
          "url": { "type": "string", "format": "uri" },
          "description": { "type": "string", "minLength": 1, "maxLength": 240 },
          "eventTypes": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "items": { "type": "string", "enum": ["payment.succeeded", "payment.failed", "payment.recovered"] }
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "webhookEndpointId": { "type": "string" },
          "paymentEventId": { "type": ["string", "null"] },
          "status": { "type": "string" },
          "attemptCount": { "type": "integer" },
          "nextAttemptAt": { "type": ["string", "null"], "format": "date-time" },
          "deliveredAt": { "type": ["string", "null"], "format": "date-time" },
          "responseStatusCode": { "type": ["integer", "null"] },
          "lastError": { "type": ["string", "null"] },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "WebhookTestEvent": {
        "type": "object",
        "properties": {
          "endpoint": { "$ref": "#/components/schemas/WebhookEndpoint" },
          "delivered": { "type": "boolean", "const": false },
          "payload": { "type": "object" },
          "headers": {
            "type": "object",
            "properties": {
              "content-type": { "type": "string" },
              "sparelane-event-id": { "type": "string" },
              "sparelane-timestamp": { "type": "string" },
              "sparelane-signature": { "type": "string", "pattern": "^v1=[a-f0-9]{64}$" }
            }
          }
        }
      }
    },
    "responses": {
      "MerchantProfileResponse": {
        "description": "Authenticated merchant profile.",
        "headers": { "sparelane-request-id": { "$ref": "#/components/headers/RequestId" } },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "data": {
                  "type": "object",
                  "properties": {
                    "merchant": { "$ref": "#/components/schemas/Merchant" },
                    "authenticatedKey": { "$ref": "#/components/schemas/MerchantApiKeySummary" }
                  }
                }
              }
            }
          }
        }
      },
      "CustomersResponse": { "description": "Customers.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "customers": { "type": "array", "items": { "$ref": "#/components/schemas/Customer" } } } } } } } } },
      "CustomerResponse": { "description": "Customer.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "customer": { "$ref": "#/components/schemas/Customer" } } } } } } } },
      "LinkAuthorizationCreatedResponse": { "description": "Link authorisation and one-time invitation token.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "authorization": { "$ref": "#/components/schemas/LinkAuthorization" }, "invitationToken": { "type": "string" } } } } } } } },
      "LinkAuthorizationResponse": { "description": "Link authorisation.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "authorization": { "$ref": "#/components/schemas/LinkAuthorization" } } } } } } } },
      "MandatesResponse": { "description": "Mandates.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "mandates": { "type": "array", "items": { "$ref": "#/components/schemas/Mandate" } } } } } } } } },
      "BillsResponse": { "description": "Bills.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "bills": { "type": "array", "items": { "$ref": "#/components/schemas/Bill" } } } } } } } } },
      "BillResponse": { "description": "Bill.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "bill": { "$ref": "#/components/schemas/Bill" } } } } } } } },
      "BillSubmittedResponse": { "description": "Bill submission result.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "bill": { "$ref": "#/components/schemas/Bill" }, "idempotent": { "type": "boolean" } } } } } } } },
      "SimulationResponse": { "description": "Simulation result.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "requestedScenario": { "$ref": "#/components/schemas/SimulatePaymentRequest/properties/scenario" }, "outcome": { "type": "string", "enum": ["succeeded", "failed", "already_processed", "already_in_progress"] }, "payment": { "oneOf": [{ "type": "null" }, { "$ref": "#/components/schemas/Payment" }] } } } } } } } },
      "PaymentsResponse": { "description": "Payments.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "payments": { "type": "array", "items": { "$ref": "#/components/schemas/Payment" } } } } } } } } },
      "PaymentResponse": { "description": "Payment.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "payment": { "$ref": "#/components/schemas/Payment" } } } } } } } },
      "WebhookEndpointsResponse": { "description": "Webhook endpoints.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "endpoints": { "type": "array", "items": { "$ref": "#/components/schemas/WebhookEndpoint" } } } } } } } } },
      "WebhookEndpointResponse": { "description": "Webhook endpoint.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "endpoint": { "$ref": "#/components/schemas/WebhookEndpoint" } } } } } } } },
      "WebhookEndpointCreatedResponse": { "description": "Webhook endpoint with one-time signing secret.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "endpoint": { "$ref": "#/components/schemas/WebhookEndpoint" }, "signingSecret": { "type": "string" }, "signingKeyDerivation": { "type": "string" } } } } } } } },
      "WebhookSecretRotatedResponse": { "description": "Webhook endpoint with rotated one-time signing secret.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "endpoint": { "$ref": "#/components/schemas/WebhookEndpoint" }, "signingSecret": { "type": "string" }, "signingKeyDerivation": { "type": "string" } } } } } } } },
      "WebhookDeliveriesResponse": { "description": "Webhook deliveries.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": { "deliveries": { "type": "array", "items": { "$ref": "#/components/schemas/WebhookDelivery" } } } } } } } } },
      "WebhookTestEventResponse": { "description": "Signed test payload and headers. Not delivered.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/WebhookTestEvent" } } } } } },
      "InvalidRequest": { "description": "Invalid request.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
      "Unauthorized": { "description": "Missing, invalid, expired or revoked bearer API key.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
      "ForbiddenMerchant": { "description": "Merchant is not active.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
      "NotFound": { "description": "Resource not found in authenticated merchant tenant.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
      "Conflict": { "description": "Request conflicts with current sandbox state or idempotency.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
      "RateLimited": { "description": "Rate limit exceeded.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
      "RateLimitUnavailable": { "description": "Distributed rate-limit store is unavailable in merchant API deployment.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
    }
  }
}
