Developer docs v0.1.0

Payments

Standalone payments and the payment log, including review resolution.

Concepts, behaviour and examples: Payments guide.

GET /v1/payments

List payments

Cursor-paginated (limit, starting_after).

Authentication
Merchant API key
Effect
Read-only.

Query parameters

NameTypeRequiredDescription
limitintegernoPage size (1–100). · ≥ 1, ≤ 100, default 20
starting_afterstringnoObject ID; return objects after it (ascending ID order). · 26 chars
subscriptionstringnoFilter by subscription ID. · 26 chars
customerstringnoFilter by customer ID. · 26 chars
status"pending" | "succeeded" | "failed" | "refunded"noFilter by status.
type"standalone" | "subscription"noStandalone vs subscription charges.
review_status"none" | "open" | "resolved"noFilter by review state; open lists payments needing a disposition.

Responses

StatusMeaningBody / error codes
200Success.object
400Invalid request.invalid_query_param
401Missing or invalid API key.authentication_error
429Rate limited; honor retry-after.rate_limit_exceeded too_many_auth_failures
500Server error.api_error
Example response
{
  "object": "list",
  "url": "string",
  "has_more": false,
  "data": [
    {
      "object": "payment",
      "id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
      "merchant_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
      "subscription_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
      "customer_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
      "instrument_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
      "amount": 1,
      "currency": "RON",
      "status": "pending",
      "netopia_ntp_id": "string",
      "netopia_status_code": 1,
      "error_message": "string",
      "period_start": "2026-10-25T18:00:00.000Z",
      "period_end": "2026-10-25T18:00:00.000Z",
      "description": "string",
      "metadata": {},
      "proration": false,
      "review": {
        "status": "open",
        "reason": "subscription_expired",
        "opened_at": "2026-10-25T18:00:00.000Z",
        "resolution": "refunded_externally",
        "note": "string",
        "resolved_at": "2026-10-25T18:00:00.000Z",
        "resolved_by_api_key_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
      },
      "created_at": "2026-10-25T18:00:00.000Z",
      "payment_link_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
      "payer_email": "string"
    }
  ]
}

POST /v1/payments

Create a standalone payment

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.

Authentication
Merchant API key
Effect
Creates a payment and charges the instrument; emits payment.succeeded or payment.failed.

Headers

NameTypeRequiredDescription
Idempotency-KeystringnoMakes retries safe: same key + body replays the stored response (idempotency-replayed: true); a different body returns 409 idempotency_key_in_use. · ≤ 96 chars

Request body

FieldTypeRequiredDescription
customer_idstringyesULID · 26 chars
instrument_idstringnoDefaults to the customer's default instrument; must belong to the customer and not be detached. · 26 chars
amountintegeryesMinor units. · > 0, ≤ 2147483647
currency"RON" | "EUR" | "USD"yesCurrency. Case-insensitive on input, stored uppercase.
descriptionstringnoAlso sent to NETOPIA Payments as the order description. · ≤ 500 chars
metadataobjectnoFree-form key/value data. Each value ≤16 KiB of JSON, ≤8 nesting levels, ≤100 properties/elements.
Example request
{
  "customer_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "instrument_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "amount": 1,
  "currency": "RON",
  "description": "string",
  "metadata": {}
}

Responses

StatusMeaningBody / error codes
201Created; check status.Payment
400Invalid body, no usable instrument, or instrument not verified.validation_failed instrument_required instrument_enrollment_required
401Missing or invalid API key.authentication_error
404Customer, or instrument not found/detached/another customer's.resource_missing
409State conflict.idempotency_key_in_use
429Rate limited; honor retry-after.rate_limit_exceeded too_many_auth_failures
500Server error.api_error
Example response
{
  "object": "payment",
  "id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "merchant_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "subscription_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "customer_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "instrument_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "amount": 1,
  "currency": "RON",
  "status": "pending",
  "netopia_ntp_id": "string",
  "netopia_status_code": 1,
  "error_message": "string",
  "period_start": "2026-10-25T18:00:00.000Z",
  "period_end": "2026-10-25T18:00:00.000Z",
  "description": "string",
  "metadata": {},
  "proration": false,
  "review": {
    "status": "open",
    "reason": "subscription_expired",
    "opened_at": "2026-10-25T18:00:00.000Z",
    "resolution": "refunded_externally",
    "note": "string",
    "resolved_at": "2026-10-25T18:00:00.000Z",
    "resolved_by_api_key_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
  },
  "created_at": "2026-10-25T18:00:00.000Z",
  "payment_link_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "payer_email": "string"
}

GET /v1/payments/:id

Retrieve a payment

Authentication
Merchant API key
Effect
Read-only.

Path parameters

NameTypeRequiredDescription
idstringyesObject ID (ULID). · 26 chars

Responses

StatusMeaningBody / error codes
200Success.Payment
401Missing or invalid API key.authentication_error
404Not found, or not yours.resource_missing
429Rate limited; honor retry-after.rate_limit_exceeded too_many_auth_failures
500Server error.api_error
Example response
{
  "object": "payment",
  "id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "merchant_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "subscription_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "customer_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "instrument_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "amount": 1,
  "currency": "RON",
  "status": "pending",
  "netopia_ntp_id": "string",
  "netopia_status_code": 1,
  "error_message": "string",
  "period_start": "2026-10-25T18:00:00.000Z",
  "period_end": "2026-10-25T18:00:00.000Z",
  "description": "string",
  "metadata": {},
  "proration": false,
  "review": {
    "status": "open",
    "reason": "subscription_expired",
    "opened_at": "2026-10-25T18:00:00.000Z",
    "resolution": "refunded_externally",
    "note": "string",
    "resolved_at": "2026-10-25T18:00:00.000Z",
    "resolved_by_api_key_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
  },
  "created_at": "2026-10-25T18:00:00.000Z",
  "payment_link_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "payer_email": "string"
}

POST /v1/payments/:id/resolve

Resolve a payment review

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.

Authentication
Merchant API key
Effect
Marks the review resolved with the calling key's ID; emits payment.review_resolved.

Path parameters

NameTypeRequiredDescription
idstringyesObject ID (ULID). · 26 chars

Headers

NameTypeRequiredDescription
Idempotency-KeystringnoMakes retries safe: same key + body replays the stored response (idempotency-replayed: true); a different body returns 409 idempotency_key_in_use. · ≤ 96 chars

Request body

FieldTypeRequiredDescription
resolution"refunded_externally" | "service_provided"yes
notestringyesIdentify the external action/reference. · ≥ 1 chars, ≤ 1000 chars
Example request
{
  "resolution": "refunded_externally",
  "note": "string"
}

Responses

StatusMeaningBody / error codes
200Success.Payment
400Invalid body.validation_failed invalid_json
401Missing or invalid API key.authentication_error
404Not found, or not yours.resource_missing
409Review is already resolved differently, or not open.payment_review_already_resolved payment_review_not_open
429Rate limited; honor retry-after.rate_limit_exceeded too_many_auth_failures
500Server error.api_error
Example response
{
  "object": "payment",
  "id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "merchant_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "subscription_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "customer_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "instrument_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "amount": 1,
  "currency": "RON",
  "status": "pending",
  "netopia_ntp_id": "string",
  "netopia_status_code": 1,
  "error_message": "string",
  "period_start": "2026-10-25T18:00:00.000Z",
  "period_end": "2026-10-25T18:00:00.000Z",
  "description": "string",
  "metadata": {},
  "proration": false,
  "review": {
    "status": "open",
    "reason": "subscription_expired",
    "opened_at": "2026-10-25T18:00:00.000Z",
    "resolution": "refunded_externally",
    "note": "string",
    "resolved_at": "2026-10-25T18:00:00.000Z",
    "resolved_by_api_key_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C"
  },
  "created_at": "2026-10-25T18:00:00.000Z",
  "payment_link_id": "01JB9XK3M2R7TQ8N4V6Y0ZPA1C",
  "payer_email": "string"
}