{
  "openapi": "3.1.0",
  "info": {
    "title": "Payware API",
    "version": "0.1.0",
    "description": "Subscriptions and payments API on top of NETOPIA Payments. All request and response bodies are JSON; IDs are 26-character ULIDs; timestamps are ISO 8601 UTC; amounts are integers in the currency's minor unit (1000 = 10.00 RON). Non-2xx responses use the `Error` envelope. See the conventions guide for idempotency, pagination, rate limits and request limits."
  },
  "tags": [
    {
      "name": "Account",
      "description": "Your own account and API keys."
    },
    {
      "name": "Payments",
      "description": "Standalone payments and the payment log, including review resolution."
    },
    {
      "name": "Payment links",
      "description": "Shareable links for one-time payments with no customer or stored card."
    },
    {
      "name": "Customers",
      "description": "People you bill."
    },
    {
      "name": "Instruments",
      "description": "Reusable payment instruments (card, account, wallet) tokenized by NETOPIA Payments."
    },
    {
      "name": "Enrollments",
      "description": "Hosted 0 RON enrollment that produces a verified instrument."
    },
    {
      "name": "Products",
      "description": "What you sell."
    },
    {
      "name": "Prices",
      "description": "Recurring amounts attached to products."
    },
    {
      "name": "Subscriptions",
      "description": "Recurring billing, billed in advance."
    },
    {
      "name": "Webhooks",
      "description": "Endpoints that receive events."
    },
    {
      "name": "Events",
      "description": "Read-only webhook delivery log."
    }
  ],
  "paths": {
    "/v1/account": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Retrieve your account",
        "description": "Your own account, identified by the API key you call with. There is no account ID in the path, so no other merchant's account is addressable.",
        "operationId": "get_v1_account",
        "x-effect": "Read-only.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "post": {
        "tags": [
          "Account"
        ],
        "summary": "Update your account",
        "description": "Partial update.",
        "operationId": "post_v1_account",
        "x-effect": "Updates account fields; a replaced `netopia_api_key` is re-encrypted.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "first_name": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "last_name": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "netopia_api_key": {
                    "type": "string",
                    "maxLength": 4096
                  },
                  "netopia_pos_signature": {
                    "type": "string",
                    "maxLength": 255
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body or no fields to update. Error codes: `validation_failed`, `invalid_json`, `empty_update`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "validation_failed",
              "invalid_json",
              "empty_update"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/account/keys": {
      "post": {
        "tags": [
          "Account"
        ],
        "summary": "Create an API key",
        "description": "Creates an additional bearer key for your account. The secret is returned once as `key`; matching keyed requests can replay it.",
        "operationId": "post_v1_account_keys",
        "x-effect": "Creates an active API key.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "default": "Default key"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Key created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyWithSecret"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body. Error codes: `validation_failed`, `invalid_json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "validation_failed",
              "invalid_json"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/account/keys/{keyId}/revoke": {
      "post": {
        "tags": [
          "Account"
        ],
        "summary": "Revoke an API key",
        "description": "Deactivates a key immediately. Revoked credentials stay invalid, including for idempotent replay.",
        "operationId": "post_v1_account_keys_keyId_revoke",
        "x-effect": "Sets the key inactive.",
        "parameters": [
          {
            "name": "keyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "API key ID"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKey"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/account/keys/{keyId}/rotate": {
      "post": {
        "tags": [
          "Account"
        ],
        "summary": "Rotate an API key",
        "description": "Revokes the key and returns a new one named `\"<name> (rotated)\"`. Keep another active key available so a lost response can be recovered with the same idempotency key.",
        "operationId": "post_v1_account_keys_keyId_rotate",
        "x-effect": "Revokes the key and creates a replacement (secret shown once).",
        "parameters": [
          {
            "name": "keyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "API key ID"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyWithSecret"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/customers": {
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Create a customer",
        "description": "Remote (NETOPIA Payments) charges later require the full `metadata.billing` object.",
        "operationId": "post_v1_customers",
        "x-effect": "Creates a customer; emits `customer.created`.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 255
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 50
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
                  }
                },
                "required": [
                  "email"
                ]
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Customer created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body. Error codes: `validation_failed`, `invalid_json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "validation_failed",
              "invalid_json"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "List customers",
        "description": "Cursor-paginated (`limit`, `starting_after`). ",
        "operationId": "get_v1_customers",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Page size (1–100)."
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID; return objects after it (ascending ID order)."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "url": {
                      "type": "string"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "Pass the last `id` of `data` as `starting_after` while true."
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Customer"
                      }
                    }
                  },
                  "required": [
                    "object",
                    "url",
                    "has_more",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameter. Error codes: `invalid_query_param`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "invalid_query_param"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/customers/{id}": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Retrieve a customer",
        "description": "",
        "operationId": "get_v1_customers_id",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Update a customer",
        "description": "Partial update.",
        "operationId": "post_v1_customers_id",
        "x-effect": "Updates the customer; emits `customer.updated`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 255
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 50
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body or no fields to update. Error codes: `validation_failed`, `invalid_json`, `empty_update`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "validation_failed",
              "invalid_json",
              "empty_update"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "delete": {
        "tags": [
          "Customers"
        ],
        "summary": "Delete a customer",
        "description": "Soft delete: the customer disappears from the API, its instruments are detached, history is kept.",
        "operationId": "delete_v1_customers_id",
        "x-effect": "Soft-deletes the customer and detaches its instruments; emits `customer.deleted`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedObject"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "409": {
            "description": "A subscription is not `canceled`/`incomplete_expired`. Error codes: `customer_has_active_subscriptions`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "customer_has_active_subscriptions"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/customers/{id}/instruments": {
      "get": {
        "tags": [
          "Instruments"
        ],
        "summary": "List a customer's instruments",
        "description": "Cursor-paginated (`limit`, `starting_after`). ",
        "operationId": "get_v1_customers_id_instruments",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Customer ID"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Page size (1–100)."
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID; return objects after it (ascending ID order)."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "url": {
                      "type": "string"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "Pass the last `id` of `data` as `starting_after` while true."
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Instrument"
                      }
                    }
                  },
                  "required": [
                    "object",
                    "url",
                    "has_more",
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/enrollments": {
      "post": {
        "tags": [
          "Enrollments"
        ],
        "summary": "Start a hosted enrollment",
        "description": "Starts a 0 RON enrollment on NETOPIA Payments' hosted page. Poll `GET /v1/enrollments/{id}`; once `requires_action`, redirect the customer to `checkout_url`. Browser return parameters are not proof of success — wait for `succeeded` and a non-null `instrument_id`.",
        "operationId": "post_v1_enrollments",
        "x-effect": "Durably queues a 0 RON enrollment (submitted after commit); emits `enrollment.created`.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "ULID",
                    "minLength": 26,
                    "maxLength": 26,
                    "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
                  },
                  "return_url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2048,
                    "description": "Optional. After hosted checkout the customer returns through Payware and is redirected here with `enrollment_id` appended. The return is not proof of success; wait for the `enrollment.updated`/`instrument.attached` webhooks or poll the enrollment. Same URL rules as webhooks."
                  }
                },
                "required": [
                  "customer_id"
                ]
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted; `status: pending`, `checkout_url: null`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Enrollment"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body or `return_url`. Error codes: `validation_failed`, `invalid_json`, `invalid_return_url`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "validation_failed",
              "invalid_json",
              "invalid_return_url"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/enrollments/{id}": {
      "get": {
        "tags": [
          "Enrollments"
        ],
        "summary": "Retrieve an enrollment",
        "description": "Returns `checkout_url` once available. Treat it as sensitive.",
        "operationId": "get_v1_enrollments_id",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Enrollment"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/events": {
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "List webhook deliveries",
        "description": "Read-only view of deliveries (one row per event per webhook), for debugging and reconciliation. Delivered/failed rows are purged after `WEBHOOK_EVENT_RETENTION_DAYS` (default 30).",
        "operationId": "get_v1_events",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Page size (1–100)."
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID; return objects after it (ascending ID order)."
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/EventType"
            },
            "description": "Filter by event type."
          },
          {
            "name": "webhook",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Filter by webhook ID."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "delivered",
                "failed"
              ]
            },
            "description": "Filter by delivery status."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "url": {
                      "type": "string"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "Pass the last `id` of `data` as `starting_after` while true."
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookEvent"
                      }
                    }
                  },
                  "required": [
                    "object",
                    "url",
                    "has_more",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Error codes: `invalid_query_param`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "invalid_query_param"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/events/{id}": {
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "Retrieve a delivery",
        "description": "Includes `payload`, `attempts` and `next_retry_at`.",
        "operationId": "get_v1_events_id",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEvent"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/instruments": {
      "post": {
        "tags": [
          "Instruments"
        ],
        "summary": "Import an instrument token (mock only)",
        "description": "Raw token import works only with `NETOPIA_MODE=mock`; remote modes return `400 instrument_enrollment_required` — use hosted enrollment (`POST /v1/enrollments`) instead. The first instrument of a customer becomes the default; `is_default: true` demotes the previous default.",
        "operationId": "post_v1_instruments",
        "x-effect": "Attaches an instrument; may change the customer's default; emits `instrument.attached`.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "ULID",
                    "minLength": 26,
                    "maxLength": 26,
                    "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
                  },
                  "netopia_token": {
                    "type": "string",
                    "maxLength": 4096
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "card",
                      "account",
                      "wallet"
                    ],
                    "description": "Hosted enrollment always produces `card`.",
                    "default": "card"
                  },
                  "card_last4": {
                    "type": "string",
                    "minLength": 4,
                    "maxLength": 4
                  },
                  "card_brand": {
                    "type": "string",
                    "maxLength": 20
                  },
                  "card_exp_month": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 12
                  },
                  "card_exp_year": {
                    "type": "integer",
                    "minimum": 2000,
                    "maximum": 3000
                  },
                  "is_default": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "customer_id",
                  "netopia_token"
                ]
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Instrument attached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Instrument"
                }
              }
            }
          },
          "400": {
            "description": "Remote mode: use hosted enrollment. Error codes: `instrument_enrollment_required`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "instrument_enrollment_required"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/instruments/{id}": {
      "get": {
        "tags": [
          "Instruments"
        ],
        "summary": "Retrieve an instrument",
        "description": "",
        "operationId": "get_v1_instruments_id",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Instrument"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "delete": {
        "tags": [
          "Instruments"
        ],
        "summary": "Detach an instrument",
        "description": "Soft delete; past charges keep referencing it. Live subscriptions (`trialing`, `active`, `past_due`, `paused`) using it switch to the customer's oldest remaining instrument.",
        "operationId": "delete_v1_instruments_id",
        "x-effect": "Detaches the instrument and re-points affected subscriptions; emits `instrument.detached` (with `replacement_instrument_id`).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedObject"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "409": {
            "description": "Live subscriptions use it and no replacement exists. Error codes: `instrument_in_use`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "instrument_in_use"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/payments": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Create a standalone payment",
        "description": "Charges a customer's stored instrument once, with no subscription (setup fee, top-up…). The instrument must have completed hosted enrollment. **A 201 does not mean the customer has paid** — read `status`, poll `GET /v1/payments/{id}` or consume `payment.succeeded`/`payment.failed`. Without a key the charge is submitted before the response; with one it is committed and submitted right after, so the first response reports `pending`. A decline is final (no retries; `retry_at: null`); timeouts/5xx stay `pending` until the NETOPIA Payments callback or reconciliation settles them. No refunds exist.",
        "operationId": "post_v1_payments",
        "x-effect": "Creates a payment and charges the instrument; emits `payment.succeeded` or `payment.failed`.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "ULID",
                    "minLength": 26,
                    "maxLength": 26,
                    "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
                  },
                  "instrument_id": {
                    "type": "string",
                    "description": "Defaults to the customer's default instrument; must belong to the customer and not be detached.",
                    "minLength": 26,
                    "maxLength": 26,
                    "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
                  },
                  "amount": {
                    "type": "integer",
                    "description": "Minor units.",
                    "exclusiveMinimum": 0,
                    "maximum": 2147483647
                  },
                  "currency": {
                    "type": "string",
                    "enum": [
                      "RON",
                      "EUR",
                      "USD"
                    ],
                    "description": "Currency. Case-insensitive on input, stored uppercase."
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Also sent to NETOPIA Payments as the order description."
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
                  }
                },
                "required": [
                  "customer_id",
                  "amount",
                  "currency"
                ]
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Created; check `status`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, no usable instrument, or instrument not verified. Error codes: `validation_failed`, `instrument_required`, `instrument_enrollment_required`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "validation_failed",
              "instrument_required",
              "instrument_enrollment_required"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Customer, or instrument not found/detached/another customer's. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "409": {
            "description": "Error codes: `idempotency_key_in_use`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "idempotency_key_in_use"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "List payments",
        "description": "Cursor-paginated (`limit`, `starting_after`). ",
        "operationId": "get_v1_payments",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Page size (1–100)."
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID; return objects after it (ascending ID order)."
          },
          {
            "name": "subscription",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Filter by subscription ID."
          },
          {
            "name": "customer",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Filter by customer ID."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "succeeded",
                "failed",
                "refunded"
              ]
            },
            "description": "Filter by status."
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "standalone",
                "subscription"
              ]
            },
            "description": "Standalone vs subscription charges."
          },
          {
            "name": "review_status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "none",
                "open",
                "resolved"
              ]
            },
            "description": "Filter by review state; `open` lists payments needing a disposition."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "url": {
                      "type": "string"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "Pass the last `id` of `data` as `starting_after` while true."
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Payment"
                      }
                    }
                  },
                  "required": [
                    "object",
                    "url",
                    "has_more",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Error codes: `invalid_query_param`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "invalid_query_param"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/payments/{id}": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Retrieve a payment",
        "description": "",
        "operationId": "get_v1_payments_id",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/payments/{id}/resolve": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Resolve a payment review",
        "description": "Records the merchant's attestation that a late successful charge (on an expired/canceled subscription) was refunded externally or service provided. No refund is submitted or verified; payment status stays `succeeded`. An identical unkeyed resolution returns the existing result.",
        "operationId": "post_v1_payments_id_resolve",
        "x-effect": "Marks the review resolved with the calling key's ID; emits `payment.review_resolved`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "resolution": {
                    "type": "string",
                    "enum": [
                      "refunded_externally",
                      "service_provided"
                    ]
                  },
                  "note": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1000,
                    "description": "Identify the external action/reference."
                  }
                },
                "required": [
                  "resolution",
                  "note"
                ]
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body. Error codes: `validation_failed`, `invalid_json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "validation_failed",
              "invalid_json"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "409": {
            "description": "Review is already resolved differently, or not open. Error codes: `payment_review_already_resolved`, `payment_review_not_open`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "payment_review_already_resolved",
              "payment_review_not_open"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/prices": {
      "post": {
        "tags": [
          "Prices"
        ],
        "summary": "Create a price",
        "description": "Amount, currency and interval are immutable; create a new price and deactivate the old one to change them.",
        "operationId": "post_v1_prices",
        "x-effect": "Creates a price.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "product_id": {
                    "type": "string",
                    "description": "ULID",
                    "minLength": 26,
                    "maxLength": 26,
                    "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
                  },
                  "amount": {
                    "type": "integer",
                    "description": "Minor units.",
                    "exclusiveMinimum": 0
                  },
                  "currency": {
                    "type": "string",
                    "enum": [
                      "RON",
                      "EUR",
                      "USD"
                    ],
                    "description": "Currency. Case-insensitive on input, stored uppercase."
                  },
                  "recurring": {
                    "type": "object",
                    "properties": {
                      "interval": {
                        "type": "string",
                        "enum": [
                          "day",
                          "week",
                          "month",
                          "year"
                        ]
                      },
                      "interval_count": {
                        "type": "integer",
                        "description": "e.g. 3 + month = quarterly.",
                        "minimum": 1,
                        "default": 1
                      }
                    },
                    "required": [
                      "interval"
                    ]
                  },
                  "active": {
                    "type": "boolean",
                    "default": true
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
                  }
                },
                "required": [
                  "product_id",
                  "amount",
                  "currency",
                  "recurring"
                ]
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Price created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Price"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body. Error codes: `validation_failed`, `invalid_json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "validation_failed",
              "invalid_json"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "get": {
        "tags": [
          "Prices"
        ],
        "summary": "List prices",
        "description": "Cursor-paginated (`limit`, `starting_after`). ",
        "operationId": "get_v1_prices",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Page size (1–100)."
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID; return objects after it (ascending ID order)."
          },
          {
            "name": "product",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Filter by product ID."
          },
          {
            "name": "active",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Filter by `true`/`false`."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "url": {
                      "type": "string"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "Pass the last `id` of `data` as `starting_after` while true."
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Price"
                      }
                    }
                  },
                  "required": [
                    "object",
                    "url",
                    "has_more",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid filter. Error codes: `invalid_query_param`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "invalid_query_param"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/prices/{id}": {
      "get": {
        "tags": [
          "Prices"
        ],
        "summary": "Retrieve a price",
        "description": "",
        "operationId": "get_v1_prices_id",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Price"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "post": {
        "tags": [
          "Prices"
        ],
        "summary": "Update a price",
        "description": "Only `active` and `metadata` can change.",
        "operationId": "post_v1_prices_id",
        "x-effect": "Updates `active`/`metadata`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "active": {
                    "type": "boolean"
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Price"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body or no fields to update. Error codes: `validation_failed`, `invalid_json`, `empty_update`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "validation_failed",
              "invalid_json",
              "empty_update"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/products": {
      "post": {
        "tags": [
          "Products"
        ],
        "summary": "Create a product",
        "description": "",
        "operationId": "post_v1_products",
        "x-effect": "Creates a product.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "description": {
                    "type": "string"
                  },
                  "active": {
                    "type": "boolean",
                    "default": true
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Product created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body. Error codes: `validation_failed`, `invalid_json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "validation_failed",
              "invalid_json"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "get": {
        "tags": [
          "Products"
        ],
        "summary": "List products",
        "description": "Cursor-paginated (`limit`, `starting_after`). ",
        "operationId": "get_v1_products",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Page size (1–100)."
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID; return objects after it (ascending ID order)."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "url": {
                      "type": "string"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "Pass the last `id` of `data` as `starting_after` while true."
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Product"
                      }
                    }
                  },
                  "required": [
                    "object",
                    "url",
                    "has_more",
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/products/{id}": {
      "get": {
        "tags": [
          "Products"
        ],
        "summary": "Retrieve a product",
        "description": "",
        "operationId": "get_v1_products_id",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "post": {
        "tags": [
          "Products"
        ],
        "summary": "Update a product",
        "description": "Partial update.",
        "operationId": "post_v1_products_id",
        "x-effect": "Updates the product.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "description": {
                    "type": "string"
                  },
                  "active": {
                    "type": "boolean",
                    "default": true
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body or no fields to update. Error codes: `validation_failed`, `invalid_json`, `empty_update`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "validation_failed",
              "invalid_json",
              "empty_update"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "delete": {
        "tags": [
          "Products"
        ],
        "summary": "Archive a product",
        "description": "Archives rather than removes: sets `active=false` and returns the product. Archived products cannot be newly subscribed to.",
        "operationId": "delete_v1_products_id",
        "x-effect": "Sets `active=false`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The archived product.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/subscriptions": {
      "post": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Create a subscription",
        "description": "Bills in advance. Without a trial a pending first charge is committed with the subscription. With an `Idempotency-Key` the response is `incomplete` and the charge is submitted after commit; without a key the synchronous response is `active` on success or `incomplete` on decline/pending. **A 201 does not mean the customer has paid** — read `status` and consume `payment.succeeded`/`payment.failed`.",
        "operationId": "post_v1_subscriptions",
        "x-effect": "Creates the subscription and its first-period charge (none during a trial); emits `subscription.created`.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "ULID",
                    "minLength": 26,
                    "maxLength": 26,
                    "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
                  },
                  "items": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "type": "object",
                      "properties": {
                        "price_id": {
                          "type": "string",
                          "description": "ULID",
                          "minLength": 26,
                          "maxLength": 26,
                          "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1,
                          "default": 1
                        },
                        "metadata": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
                        }
                      },
                      "required": [
                        "price_id"
                      ]
                    },
                    "description": "All prices must share currency and interval, be active, and belong to non-archived products."
                  },
                  "default_instrument_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Defaults to the customer's default instrument. Required unless a trial starts.",
                    "minLength": 26,
                    "maxLength": 26,
                    "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "trial_period_days": {
                    "type": "integer",
                    "description": "Mutually exclusive with `trial_end`.",
                    "exclusiveMinimum": 0
                  },
                  "trial_end": {
                    "type": "string",
                    "format": "date-time"
                  }
                },
                "required": [
                  "customer_id",
                  "items"
                ]
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Created (not necessarily paid).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscription"
                }
              }
            }
          },
          "400": {
            "description": "Rule violation. Error codes: `instrument_required`, `price_inactive`, `product_inactive`, `mixed_billing_dimensions`, `instrument_enrollment_required`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "instrument_required",
              "price_inactive",
              "product_inactive",
              "mixed_billing_dimensions",
              "instrument_enrollment_required"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "409": {
            "description": "Another change holds the subscription lock; retry. Error codes: `subscription_locked`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "subscription_locked"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "get": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "List subscriptions",
        "description": "Cursor-paginated (`limit`, `starting_after`). ",
        "operationId": "get_v1_subscriptions",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Page size (1–100)."
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID; return objects after it (ascending ID order)."
          },
          {
            "name": "customer",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Filter by customer ID."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "incomplete",
                "incomplete_expired",
                "trialing",
                "active",
                "past_due",
                "unpaid",
                "paused",
                "canceled"
              ]
            },
            "description": "Filter by status."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "url": {
                      "type": "string"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "Pass the last `id` of `data` as `starting_after` while true."
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Subscription"
                      }
                    }
                  },
                  "required": [
                    "object",
                    "url",
                    "has_more",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Error codes: `invalid_query_param`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "invalid_query_param"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/subscriptions/{id}": {
      "get": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Retrieve a subscription",
        "description": "",
        "operationId": "get_v1_subscriptions_id",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscription"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "post": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Update a subscription",
        "description": "Partial update. **Changing `items`** replaces all items; they must keep the subscription's currency and interval. On an `active` subscription the price difference for the rest of the period is charged first (`proration_behavior: create_prorations`, default); a declined unkeyed charge returns 402 and leaves items unchanged, a pending result holds the change until it settles. Mid-period downgrades return `409 proration_credit_unsupported` — send `proration_behavior: none` to change items with no charge/credit (billed from the next period). `trialing`, `paused` and `past_due` change items without proration. **Pausing:** `pause_collection: {behavior: \"void\", resumes_at}`; `null` resumes.",
        "operationId": "post_v1_subscriptions_id",
        "x-effect": "Updates the subscription; may create a proration charge, pause or resume it; emits `subscription.updated`/`paused`/`resumed`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "default_instrument_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "ULID",
                    "minLength": 26,
                    "maxLength": 26,
                    "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
                  },
                  "items": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "properties": {
                        "price_id": {
                          "type": "string",
                          "description": "ULID",
                          "minLength": 26,
                          "maxLength": 26,
                          "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1,
                          "default": 1
                        },
                        "metadata": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
                        }
                      },
                      "required": [
                        "price_id"
                      ]
                    }
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "cancel_at_period_end": {
                    "type": "boolean"
                  },
                  "pause_collection": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "behavior": {
                        "const": "void"
                      },
                      "resumes_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Optional; must be in the future."
                      }
                    },
                    "required": [
                      "behavior"
                    ]
                  },
                  "proration_behavior": {
                    "type": "string",
                    "enum": [
                      "create_prorations",
                      "none"
                    ],
                    "default": "create_prorations"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionWithProration"
                }
              }
            }
          },
          "400": {
            "description": "Rule violation. Error codes: `mixed_billing_dimensions`, `instrument_required`, `price_inactive`, `product_inactive`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "mixed_billing_dimensions",
              "instrument_required",
              "price_inactive",
              "product_inactive"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "402": {
            "description": "Unkeyed proration charge declined; items unchanged. Error codes: `proration_payment_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "proration_payment_failed"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "409": {
            "description": "State conflict. Error codes: `subscription_not_updatable`, `proration_in_progress`, `proration_credit_unsupported`, `invalid_status_transition`, `subscription_locked`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "subscription_not_updatable",
              "proration_in_progress",
              "proration_credit_unsupported",
              "invalid_status_transition",
              "subscription_locked"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "delete": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Cancel a subscription",
        "description": "Cancels immediately (no refund). With `at_period_end=true` the paid period is kept and a fixed `cancel_at` deadline is set; it cancels without a further charge once due.",
        "operationId": "delete_v1_subscriptions_id",
        "x-effect": "Cancels now (`subscription.canceled`) or schedules cancellation (`subscription.updated`).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          },
          {
            "name": "at_period_end",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Cancel at period end instead of now."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The canceled or scheduled subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscription"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "409": {
            "description": "Error codes: `invalid_status_transition`, `subscription_locked`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "invalid_status_transition",
              "subscription_locked"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/subscriptions/{id}/resume": {
      "post": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Resume a paused subscription",
        "description": "If the paid period has already ended a new period starts immediately and is charged; otherwise the existing period is kept. A due scheduled cancellation cancels instead.",
        "operationId": "post_v1_subscriptions_id_resume",
        "x-effect": "Clears the pause; may start and charge a new period; emits `subscription.resumed`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscription"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "409": {
            "description": "Error codes: `invalid_status_transition`, `subscription_locked`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "invalid_status_transition",
              "subscription_locked"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/webhooks": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Create a webhook",
        "description": "Registers a URL to receive events. The signing secret is returned once.",
        "operationId": "post_v1_webhooks",
        "x-effect": "Creates an active webhook.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2048,
                    "description": "`http`/`https`, no credentials. In production: `https`, no localhost/`*.local`/`*.internal`/private addresses (unless `WEBHOOK_ALLOW_PRIVATE_HOSTS=true`)."
                  },
                  "enabled_events": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/EventType"
                    },
                    "description": "Omit or leave empty to receive all events."
                  },
                  "is_active": {
                    "type": "boolean",
                    "description": "`false` pauses delivery."
                  }
                },
                "required": [
                  "url"
                ]
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Webhook created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookWithSecret"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body or URL. Error codes: `invalid_webhook_url`, `validation_failed`, `invalid_json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "invalid_webhook_url",
              "validation_failed",
              "invalid_json"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhooks",
        "description": "Cursor-paginated (`limit`, `starting_after`). ",
        "operationId": "get_v1_webhooks",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Page size (1–100)."
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID; return objects after it (ascending ID order)."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "url": {
                      "type": "string"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "Pass the last `id` of `data` as `starting_after` while true."
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    }
                  },
                  "required": [
                    "object",
                    "url",
                    "has_more",
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Retrieve a webhook",
        "description": "The secret is omitted.",
        "operationId": "get_v1_webhooks_id",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Update a webhook",
        "description": "Partial update.",
        "operationId": "post_v1_webhooks_id",
        "x-effect": "Updates the webhook.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2048,
                    "description": "`http`/`https`, no credentials. In production: `https`, no localhost/`*.local`/`*.internal`/private addresses (unless `WEBHOOK_ALLOW_PRIVATE_HOSTS=true`)."
                  },
                  "enabled_events": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/EventType"
                    },
                    "description": "Omit or leave empty to receive all events."
                  },
                  "is_active": {
                    "type": "boolean",
                    "description": "`false` pauses delivery."
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "400": {
            "description": "Error codes: `empty_update`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "empty_update"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete a webhook",
        "description": "Removes the webhook and its delivery history.",
        "operationId": "delete_v1_webhooks_id",
        "x-effect": "Deletes the webhook and its events.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedObject"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/webhooks/{id}/rotate-secret": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Rotate the signing secret",
        "description": "The old secret stops working immediately; failed deliveries are retried and re-signed with the new one.",
        "operationId": "post_v1_webhooks_id_rotate_secret",
        "x-effect": "Replaces the signing secret.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookWithSecret"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/payment-links": {
      "post": {
        "tags": [
          "Payment links"
        ],
        "summary": "Create a payment link",
        "description": "Creates a link for a one-time payment with no customer and no stored card. Share `url` with the payer: a Payware page asks for their email, name and billing details, then sends them to NETOPIA Payments' secure page to pay. The link is single use, reopens if a payment attempt fails, and expires after `expires_in_days` (default 7). The result appears in `GET /v1/payments` as a payment with `customer_id: null`; read `status` there or consume `payment.succeeded`/`payment.failed`. An abandoned checkout stays `pending` until it is reconciled.",
        "operationId": "post_v1_payment_links",
        "x-effect": "Creates a payment link; nothing is charged until the payer pays.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "kind",
                  "amount",
                  "currency"
                ],
                "properties": {
                  "kind": {
                    "const": "payment"
                  },
                  "amount": {
                    "type": "integer",
                    "description": "Minor units.",
                    "exclusiveMinimum": 0,
                    "maximum": 2147483647
                  },
                  "currency": {
                    "type": "string",
                    "enum": [
                      "RON",
                      "EUR",
                      "USD"
                    ]
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "return_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Where the payer's browser goes after the hosted page, with `payment_link_id` appended. Must be a public http(s) URL. Arriving is not proof of payment."
                  },
                  "expires_in_days": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 90,
                    "default": 7
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentLink"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, no usable instrument, or instrument not verified. Error codes: `validation_failed`, `instrument_required`, `instrument_enrollment_required`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "validation_failed",
              "instrument_required",
              "instrument_enrollment_required"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      },
      "get": {
        "tags": [
          "Payment links"
        ],
        "summary": "List payment links",
        "description": "Cursor-paginated (`limit`, `starting_after`); filter by `status`.",
        "operationId": "get_v1_payment_links",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Page size (1–100)."
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID; return objects after it (ascending ID order)."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "processing",
                "completed",
                "expired",
                "canceled"
              ]
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PaymentLink"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error codes: `invalid_query_param`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "invalid_query_param"
            ]
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/payment-links/{id}": {
      "get": {
        "tags": [
          "Payment links"
        ],
        "summary": "Retrieve a payment link",
        "description": "",
        "operationId": "get_v1_payment_links_id",
        "x-effect": "Read-only.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentLink"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    },
    "/v1/payment-links/{id}/cancel": {
      "post": {
        "tags": [
          "Payment links"
        ],
        "summary": "Cancel a payment link",
        "description": "Cancels an `open` link. A link with a payment in progress cannot be canceled (409).",
        "operationId": "post_v1_payment_links_id_cancel",
        "x-effect": "Marks the link canceled.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "ULID",
              "minLength": 26,
              "maxLength": 26,
              "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
            },
            "description": "Object ID (ULID)."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 96
            },
            "description": "Makes retries safe: same key + body replays the stored response (`idempotency-replayed: true`); a different body returns 409 `idempotency_key_in_use`."
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentLink"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key. Error codes: `authentication_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "authentication_error"
            ]
          },
          "404": {
            "description": "Not found, or not yours. Error codes: `resource_missing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "resource_missing"
            ]
          },
          "409": {
            "description": "Review is already resolved differently, or not open. Error codes: `payment_review_already_resolved`, `payment_review_not_open`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "payment_review_already_resolved",
              "payment_review_not_open"
            ]
          },
          "429": {
            "description": "Rate limited; honor `retry-after`. Error codes: `rate_limit_exceeded`, `too_many_auth_failures`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "rate_limit_exceeded",
              "too_many_auth_failures"
            ]
          },
          "500": {
            "description": "Server error. Error codes: `api_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "x-error-codes": [
              "api_error"
            ]
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request_error",
                  "authentication_error",
                  "idempotency_error",
                  "card_error",
                  "api_error"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation."
              },
              "code": {
                "type": "string",
                "description": "Stable machine-readable identifier, e.g. `resource_missing`."
              },
              "param": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "First offending field; dotted for nested fields (`recurring.interval`)."
              },
              "request_id": {
                "type": "string",
                "description": "Quote this when reporting problems."
              }
            },
            "required": [
              "type",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "DeletedObject": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string"
          },
          "id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "deleted": {
            "type": "boolean",
            "example": true
          }
        },
        "required": [
          "object",
          "id",
          "deleted"
        ]
      },
      "Account": {
        "type": "object",
        "properties": {
          "object": {
            "const": "account"
          },
          "id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Null until the person who signed in by email has entered their name."
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_live": {
            "type": "boolean",
            "description": "True for a live account (`sk_live_` keys), false for a test account (`sk_test_` keys, NETOPIA Payments sandbox)."
          },
          "netopia_pos_signature": {
            "type": [
              "string",
              "null"
            ],
            "description": "Null until the NETOPIA Payments credentials are saved."
          },
          "netopia_configured": {
            "type": "boolean",
            "description": "False while the account has no NETOPIA Payments credentials. Payments and enrollments fail with `netopia_not_configured` (409) until they are saved with `POST /v1/account`."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "can_switch_mode": {
            "type": "boolean",
            "description": "True when the request comes from a dashboard session that can switch between the live account and its linked test account. Always false for API keys."
          },
          "can_sync": {
            "type": "boolean",
            "description": "True for a dashboard session, which can sync products, prices and webhook endpoints from live into the test account. Always false for API keys."
          }
        },
        "description": "A merchant account. The NETOPIA Payments API key is write-only and never returned."
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "object": {
            "const": "api_key"
          },
          "id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "merchant_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "key_prefix": {
            "type": "string",
            "description": "First 12 characters, for identification."
          },
          "name": {
            "type": "string"
          },
          "is_active": {
            "type": "boolean"
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApiKeyWithSecret": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ApiKey"
          },
          {
            "type": "object",
            "properties": {
              "key": {
                "type": "string",
                "description": "The secret, shown once (matching keyed requests can replay it)."
              }
            },
            "required": [
              "key"
            ]
          }
        ]
      },
      "Customer": {
        "type": "object",
        "properties": {
          "object": {
            "const": "customer"
          },
          "id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "merchant_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "Remote charges require the full billing object in `metadata.billing` (see NETOPIA.md)."
      },
      "Product": {
        "type": "object",
        "properties": {
          "object": {
            "const": "product"
          },
          "id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "merchant_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "active": {
            "type": "boolean"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Price": {
        "type": "object",
        "properties": {
          "object": {
            "const": "price"
          },
          "id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "merchant_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "product_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "active": {
            "type": "boolean"
          },
          "amount": {
            "type": "integer",
            "description": "Minor units."
          },
          "currency": {
            "type": "string",
            "enum": [
              "RON",
              "EUR",
              "USD"
            ],
            "description": "Currency. Case-insensitive on input, stored uppercase."
          },
          "recurring": {
            "type": "object",
            "properties": {
              "interval": {
                "type": "string",
                "enum": [
                  "day",
                  "week",
                  "month",
                  "year"
                ]
              },
              "interval_count": {
                "type": "integer",
                "minimum": 1
              }
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "Amount, currency and interval are immutable; create a new price to change them."
      },
      "Instrument": {
        "type": "object",
        "properties": {
          "object": {
            "const": "instrument"
          },
          "id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "merchant_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "customer_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "type": {
            "type": "string",
            "enum": [
              "card",
              "account",
              "wallet"
            ]
          },
          "card": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "last4": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "brand": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "exp_month": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "exp_year": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "description": "Present only when `type` is `card`; display data only."
          },
          "is_default": {
            "type": "boolean"
          },
          "enrollment_verified_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Set after verified hosted enrollment; remote billing requires it."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "A reusable NETOPIA Payments token. The token itself is never returned."
      },
      "Enrollment": {
        "type": "object",
        "properties": {
          "object": {
            "const": "enrollment"
          },
          "id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "merchant_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "customer_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "requires_action",
              "processing",
              "succeeded",
              "failed"
            ]
          },
          "amount": {
            "const": 0
          },
          "currency": {
            "const": "RON"
          },
          "netopia_ntp_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "instrument_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set once `status` is `succeeded`.",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "error_message": {
            "type": [
              "string",
              "null"
            ]
          },
          "checkout_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Hosted NETOPIA Payments checkout; sensitive, present once `status` is `requires_action`. Do not log or share."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "return_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Where the customer lands after hosted checkout, via Payware."
          }
        },
        "description": "A 0 RON hosted card enrollment that yields a verified reusable instrument."
      },
      "SubscriptionItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "price_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "quantity": {
            "type": "integer",
            "minimum": 1
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
          }
        }
      },
      "Subscription": {
        "type": "object",
        "properties": {
          "object": {
            "const": "subscription"
          },
          "id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "merchant_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "customer_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "status": {
            "type": "string",
            "enum": [
              "incomplete",
              "incomplete_expired",
              "trialing",
              "active",
              "past_due",
              "unpaid",
              "paused",
              "canceled"
            ],
            "description": "A 201 does not mean the customer has paid; check `status`."
          },
          "default_instrument_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "collection_method": {
            "const": "charge_automatically"
          },
          "billing_cycle_anchor": {
            "type": "string",
            "format": "date-time"
          },
          "current_period_start": {
            "type": "string",
            "format": "date-time"
          },
          "current_period_end": {
            "type": "string",
            "format": "date-time"
          },
          "trial_start": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "trial_end": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cancel_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "next_payment_attempt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Set only while `past_due`."
          },
          "cancel_at_period_end": {
            "type": "boolean"
          },
          "canceled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ended_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "pause_collection": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "behavior": {
                "const": "void"
              },
              "resumes_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubscriptionItem"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SubscriptionWithProration": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Subscription"
          },
          {
            "type": "object",
            "properties": {
              "proration_transaction": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/Payment"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The proration charge when one was created, otherwise null."
              }
            }
          }
        ]
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "object": {
            "const": "webhook"
          },
          "id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "merchant_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "enabled_events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EventType"
            },
            "description": "Empty means all events."
          },
          "is_active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookWithSecret": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Webhook"
          },
          {
            "type": "object",
            "properties": {
              "secret": {
                "type": "string",
                "description": "Signing secret (`whsec_…`), returned only here and on matching idempotency replays."
              }
            },
            "required": [
              "secret"
            ]
          }
        ]
      },
      "EventType": {
        "type": "string",
        "enum": [
          "customer.created",
          "customer.updated",
          "customer.deleted",
          "subscription.created",
          "subscription.updated",
          "subscription.canceled",
          "subscription.paused",
          "subscription.resumed",
          "subscription.trial_will_end",
          "payment.succeeded",
          "payment.failed",
          "payment.review_required",
          "payment.review_resolved",
          "enrollment.created",
          "enrollment.updated",
          "enrollment.failed",
          "instrument.attached",
          "instrument.detached"
        ]
      },
      "WebhookEvent": {
        "type": "object",
        "properties": {
          "object": {
            "const": "webhook_event"
          },
          "id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "merchant_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "webhook_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "event_type": {
            "$ref": "#/components/schemas/EventType"
          },
          "payload": {
            "type": "object",
            "description": "The delivered envelope."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "delivered",
              "failed"
            ]
          },
          "attempts": {
            "type": "integer"
          },
          "last_attempt_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "next_retry_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "One delivery record per event per webhook."
      },
      "PaymentReview": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "open",
              "resolved"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "subscription_expired",
              "subscription_canceled"
            ]
          },
          "opened_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "resolution": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "refunded_externally",
              "service_provided"
            ]
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "resolved_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "resolved_by_api_key_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          }
        }
      },
      "Payment": {
        "type": "object",
        "properties": {
          "object": {
            "const": "payment"
          },
          "id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "merchant_id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "subscription_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Null for standalone payments.",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "customer_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Null for an anonymous payment made through a payment link.",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "instrument_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "amount": {
            "type": "integer",
            "description": "Minor units."
          },
          "currency": {
            "type": "string",
            "enum": [
              "RON",
              "EUR",
              "USD"
            ],
            "description": "Currency. Case-insensitive on input, stored uppercase."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "succeeded",
              "failed",
              "refunded"
            ],
            "description": "`refunded` is reserved."
          },
          "netopia_ntp_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "netopia_status_code": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error_message": {
            "type": [
              "string",
              "null"
            ]
          },
          "period_start": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Null for standalone payments."
          },
          "period_end": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Null for standalone payments."
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Free-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements."
          },
          "proration": {
            "type": "boolean"
          },
          "review": {
            "$ref": "#/components/schemas/PaymentReview",
            "type": null
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "payment_link_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The payment link this payment was made through, otherwise null.",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "payer_email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Email the payer entered on a payment link, otherwise null."
          }
        },
        "description": "One attempt to charge a stored instrument: either a subscription charge or a standalone payment."
      },
      "WebhookEnvelope": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Logical event ID; the same for every webhook that receives it.",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "object": {
            "const": "event"
          },
          "type": {
            "$ref": "#/components/schemas/EventType"
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "type": "object",
            "properties": {
              "object": {
                "type": "object",
                "description": "The full API object (subscription, customer, payment, instrument or enrollment); deletions carry `{object, id, deleted: true}`."
              },
              "retry_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "`payment.failed` only: next attempt, `null` when exhausted or standalone."
              },
              "replacement_instrument_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "`instrument.detached` only.",
                "minLength": 26,
                "maxLength": 26,
                "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
              }
            }
          }
        },
        "required": [
          "id",
          "object",
          "type",
          "created",
          "data"
        ]
      },
      "PaymentLink": {
        "type": "object",
        "properties": {
          "object": {
            "const": "payment_link"
          },
          "id": {
            "type": "string",
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "kind": {
            "type": "string",
            "enum": [
              "payment"
            ],
            "description": "`payment`: one charge. (Subscription links are not available yet.)"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "processing",
              "completed",
              "expired",
              "canceled"
            ],
            "description": "`open` can be paid; `processing` has a payment in progress (it returns to `open` if that payment fails); `completed` was paid."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Give this to the payer. The secret is in the URL fragment."
          },
          "amount": {
            "type": "integer",
            "description": "Minor units."
          },
          "currency": {
            "type": "string",
            "enum": [
              "RON",
              "EUR",
              "USD"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "return_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "payer_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "payer_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "ULID",
            "minLength": 26,
            "maxLength": 26,
            "example": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Merchant API key (`sk_test_…` / `sk_live_…`). Every resource is scoped to the key's merchant; other merchants' objects return 404."
      }
    }
  },
  "webhooks": {
    "event": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Event delivery",
        "operationId": "webhook_event",
        "x-effect": "Your endpoint receives one signed envelope per event.",
        "description": "Payware POSTs one JSON envelope per event to each matching webhook. Headers: `x-signature: t=<unix>,v1=<hex HMAC-SHA256 of <t>.<raw body>`, `x-event-type`, `x-webhook-id` (unique per delivery, stable across retries). Respond with any 2xx within `WEBHOOK_TIMEOUT_MS` (default 10 s); redirects are never followed. Delivery is at-least-once, retried after 1 min, 5 min, 30 min, 2 h and 24 h (then `failed`); dedupe on `id` and expect out-of-order arrival.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    }
  }
}
