OpenAPI spec
OpenAPI 3.1 description of every endpoint: inputs, outputs, effects and status/error codes. Open the raw file for tooling.
openapi.json
Payware API · v0.1.0 · 29 paths
{
"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."
}
}
}
}
}
}