{"openapi":"3.1.0","info":{"title":"Payshen API","version":"1.0.0","summary":"Payment orchestration over the providers you already use.","description":"One server-to-server REST API across every payment provider you connect.\n\n**Authentication.** `Authorization: Bearer <key>`. A `ps_test_` key reads and writes\nsandbox data only; a `ps_live_` key production only. The two are fully partitioned -\na test key can never see a production payment, and outcomes produced in sandbox never\ntrain the routing engine or appear in live reporting.\n\n**Money.** Every amount is an integer in the currency's minor unit. That is hundredths\nfor EUR and USD, whole units for JPY and KRW, and thousandths for KWD. `5000` is\nEUR 50.00 but JPY 5,000.\n\n**Envelope.** Success is `{ \"data\": ... }`. Lists add `has_more` and `next_cursor`.\nFailure is `{ \"error\": { \"code\", \"message\" } }` - switch on `code`.\n\n**Retries.** Send `Idempotency-Key` on payment creation; it is required when the API\nkey's environment is production (a `ps_live_` key) and optional in sandbox (`ps_test_`).\nA replay of the same key and\nbody returns the original response with `Idempotent-Replay: true`; the same key with a\ndifferent body is rejected 422 rather than silently charging twice. Once a provider may\nhave received a request, a retry under its key is never sent to a provider again while\nthe key is kept: 24 hours after its answer, and for as long as the provider attempt has\nno answer or its payment is `UNKNOWN`. After that the same key is a new request.\n\n**Card data never reaches this API.** Send `return_url` to receive a `next_action` of\ntype `REDIRECT`; the customer completes payment on a provider-controlled page.\n\n**Stability.** This is v1 and it is frozen: paths, request fields, response fields\nand error codes will not be removed or change meaning within it. New optional fields\nand new endpoints may be added, so parse defensively and ignore what you do not\nrecognise. Anything breaking would ship as /api/v2 alongside this, never in place of\nit. The pre-unification `/api/v1/transactions` aliases were removed before this\nfreeze; use `/api/v1/payments`.","contact":{"name":"Payshen support","url":"https://www.payshen.com/#contact"}},"servers":[{"url":"https://www.payshen.com","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Payments","description":"Create, read and move money on a payment."},{"name":"Events","description":"The append-only timeline, also delivered by webhook."},{"name":"Partners","description":"White-label partners provisioning their own merchants."},{"name":"Providers","description":"Endpoints your payment providers call, not you."}],"paths":{"/api/v1/payments":{"post":{"tags":["Payments"],"summary":"Create a payment","description":"Routes the payment across your active connections. A second connection is tried only after a soft decline or a technical error before anything was executed (per routing mode), never after a timeout or an unknown result, which answers `UNKNOWN`. With `return_url` the response is a redirect instead of an outcome.","operationId":"createPayment","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"8-200 printable ASCII characters. Scoped to your workspace and environment. Required when the API key's environment is production (a `ps_live_` key): such a request without it is refused 400 `idempotency_key_required`. Optional in sandbox (`ps_test_`). Once a provider may have received the request, every retry under this key is answered from what Payshen recorded and never sent to a provider again: the stored response, else the payment's current state, else `UNKNOWN`. The key is kept 24 hours after its answer, and for as long as the provider attempt has no answer or its payment is `UNKNOWN`; after that the same key is a new request."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePayment"}}}},"responses":{"201":{"description":"Created. Either an outcome, or a redirect to complete it.","headers":{"Idempotent-Replay":{"schema":{"type":"string","enum":["true"]},"description":"Present when this is a replay of an earlier identical request."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentEnvelope"}}}},"400":{"description":"`idempotency_key_required` (a live key sent no Idempotency-Key), `invalid_idempotency_key` or `invalid_json`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, revoked or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The workspace is suspended, or the key lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_in_progress`, or `routing_failed` when no connection can take it. With `debug: true` a `routing_failed` error also carries `error.excluded`, the same list as `routing.excluded`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` - the body exceeds 16 KB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`validation_error`, or `idempotency_key_reuse` for a reused key with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`internal_error` - nothing was sent to a provider; safe to retry with the same Idempotency-Key. `payment_result_unknown` - a provider may have received the payment and its result could not be recorded yet; the error carries `request_id`. Do not retry with a new Idempotency-Key. A retry with the same key never charges again: it answers 409 while the first request's claim is fresh (15 minutes), then the payment as Payshen recorded it (often `UNKNOWN`). A daily job records such a payment even if nobody retries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Payments"],"summary":"List payments","description":"Newest first, cursor paginated on payment id. Scoped to the key's environment.","operationId":"listPayments","parameters":[{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/PaymentStatus"},"description":"Case-insensitive."},{"name":"before","in":"query","schema":{"type":"string"},"description":"Cursor: the last id from the previous page."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"A page of payments.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentListEnvelope"}}}},"401":{"description":"Missing, malformed, revoked or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The workspace is suspended, or the key lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`validation_error` - an unknown status filter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/payments/{id}":{"get":{"tags":["Payments"],"summary":"Retrieve a payment","operationId":"getPayment","parameters":[{"$ref":"#/components/parameters/PaymentId"}],"responses":{"200":{"description":"The payment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentEnvelope"}}}},"401":{"description":"Missing, malformed, revoked or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The workspace is suspended, or the key lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`. Also returned for a payment belonging to another workspace or environment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/payments/{id}/capture":{"post":{"tags":["Payments"],"summary":"Capture an authorization","description":"Full or partial. Only a payment created with `capture_method: manual` can be captured.","operationId":"capturePayment","parameters":[{"$ref":"#/components/parameters/PaymentId"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"integer","minimum":1,"description":"Minor units. Omit to capture the full authorization."}}}}}},"responses":{"200":{"description":"Captured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentEnvelope"}}}},"400":{"description":"`invalid_idempotency_key` - the Idempotency-Key is not 8-200 printable ASCII characters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, revoked or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The workspace is suspended, or the key lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`not_capturable` - the payment is not `AUTHORIZED` (already captured, voided, or a capture or void is in flight or unknown); `capture_conflict` - another capture or void took it first; `no_provider_ref` - there is no provider reference to capture; `idempotency_in_progress` - a request with this Idempotency-Key is still being processed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`validation_error` or `invalid_amount` - the amount is not a positive integer or exceeds the authorization; `idempotency_key_reuse` - the key was used with a different amount.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`state_unconfirmed` - the provider was asked and its answer could not be recorded (the message says whether it confirmed, refused or did not answer); `state_conflict` - the provider confirmed the capture and the payment had meanwhile moved. Do not retry: the same Idempotency-Key replays this answer, and the payment is checked with the provider by Payshen operations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"`unsupported` - the payment's provider cannot capture.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"`psp_error` - the provider refused the capture (the message is the provider's): nothing was captured and the payment is `AUTHORIZED` again; the Idempotency-Key is released, so a new attempt is safe. `capture_result_unknown` - the provider did not answer and may have captured: the payment is `UNKNOWN`. Do NOT retry; the same Idempotency-Key replays this answer, and Payshen settles the payment from the provider's record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/payments/{id}/void":{"post":{"tags":["Payments"],"summary":"Void an authorization","description":"Releases an uncaptured authorization. A captured payment must be refunded instead.","operationId":"voidPayment","parameters":[{"$ref":"#/components/parameters/PaymentId"}],"responses":{"200":{"description":"Voided.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentEnvelope"}}}},"400":{"description":"`invalid_idempotency_key` - the Idempotency-Key is not 8-200 printable ASCII characters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, revoked or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The workspace is suspended, or the key lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`not_voidable` - the payment is not `AUTHORIZED` (already captured, voided, or a capture or void is in flight or unknown); `void_conflict` - another capture or void took it first; `no_provider_ref` - there is no provider reference to void; `idempotency_in_progress` - a request with this Idempotency-Key is still being processed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`state_unconfirmed` - the provider was asked and its answer could not be recorded (the message says whether it confirmed, refused or did not answer); `state_conflict` - the provider confirmed the void and the payment had meanwhile moved. Do not retry: the same Idempotency-Key replays this answer, and the payment is checked with the provider by Payshen operations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"`unsupported` - the payment's provider cannot void.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"`psp_error` - the provider refused the void (the message is the provider's): the payment is `AUTHORIZED` again; the Idempotency-Key is released, so a new attempt is safe. `void_result_unknown` - the provider did not answer and may have voided: the payment is `UNKNOWN`. Do NOT retry; the same Idempotency-Key replays this answer, and Payshen settles the payment from the provider's record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/payments/{id}/refunds":{"post":{"tags":["Payments"],"summary":"Refund a captured payment","description":"Full or partial. The amount is clamped to the refundable balance, and concurrent full refunds cannot both succeed. Send an `Idempotency-Key`: on this endpoint it names the refund itself, so a request repeated under the same key is answered from that refund and never refunds twice. For 24 hours the stored response is replayed as it was sent; after that, the same key on the same payment is answered from the refund's current record (its final result once it is settled). A refund the provider has not confirmed yet is counted in `refund_pending_amount`, not in `refunded_amount`, and is settled from the provider's own record: a refund still pending is never answered as refunded.","operationId":"refundPayment","parameters":[{"$ref":"#/components/parameters/PaymentId"},{"name":"Idempotency-Key","in":"header","required":false,"description":"8-200 printable ASCII characters. The same key on the same payment is the same refund: a repeat answers that refund's result (or `409 refund_pending` while the provider has not confirmed it) and never reaches the provider again. Within 24 hours a repeat replays the stored response; after 24 hours it is answered from the refund's record. The same key with a different amount is refused.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"integer","minimum":1,"description":"Minor units. Omit to refund the full refundable balance."},"reason":{"type":"string","maxLength":200}}}}}},"responses":{"200":{"description":"Refunded: the provider confirmed it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentEnvelope"}}}},"401":{"description":"Missing, malformed, revoked or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The workspace is suspended, or the key lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`refund_pending` - this refund, or one holding the rest of the balance, is not confirmed yet: the provider accepted it and is still processing it, or its answer is still being settled. It is not refunded, and its amount is in `refund_pending_amount`. Do not retry with a new key; the same Idempotency-Key answers its final result once it is settled. Also `not_refundable` and `refund_conflict`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`validation_error` or `invalid_amount` - nothing refundable, or an invalid amount; `idempotency_key_reuse` - the key was used with a different amount.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`state_unconfirmed` - the provider confirmed the refund, and either recording it failed or the payment's status could not be updated after it (the message says which). Do not retry: the same Idempotency-Key replays this answer for 24 hours, and after that is answered from the refund's record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"`psp_error` - the provider refused the refund: nothing was refunded and the amount is refundable again. The same Idempotency-Key answers the same refusal; a retry with a NEW Idempotency-Key is a new refund and is safe. `refund_result_unknown` - the provider did not answer and may have refunded: the amount stays in `refund_pending_amount`. Do NOT retry, with any key; Payshen settles it from the provider's record and the payment shows the result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/payments/{id}/events":{"get":{"tags":["Events"],"summary":"List a payment's timeline","operationId":"listPaymentEvents","parameters":[{"$ref":"#/components/parameters/PaymentId"}],"responses":{"200":{"description":"Events, oldest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventListEnvelope"}}}},"401":{"description":"Missing, malformed, revoked or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The workspace is suspended, or the key lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/events":{"get":{"tags":["Events"],"summary":"List events across all payments","description":"The polling equivalent of your webhook endpoint. Scoped to the key's environment.","operationId":"listEvents","parameters":[{"name":"type","in":"query","schema":{"type":"string"},"description":"Exact event type, e.g. `payment.captured`."},{"name":"before","in":"query","schema":{"type":"string"},"description":"Cursor: the last id from the previous page."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"A page of events.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventListEnvelope"}}}},"401":{"description":"Missing, malformed, revoked or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The workspace is suspended, or the key lacks the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`validation_error`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/merchants":{"post":{"tags":["Partners"],"summary":"Provision a sub-merchant","description":"White-label partners only. Requires a live key and the partner flag on your workspace, both enforced server-side.","operationId":"createMerchant","responses":{"201":{"description":"The sub-merchant workspace was created and its owner invited."},"401":{"description":"Missing, malformed, revoked or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`not_a_partner`, or `live_key_required` when called with a test key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`already_invited`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`validation_error`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`merchant_limit` - the partner's allowance is exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Partners"],"summary":"List your sub-merchants","operationId":"listMerchants","responses":{"200":{"description":"The partner's merchants."},"401":{"description":"Missing, malformed, revoked or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`not_a_partner`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/openapi.json":{"get":{"tags":["Payments"],"summary":"This document","description":"The machine-readable contract, unauthenticated so it can be read and imported before you have an account.","operationId":"getOpenApiDocument","security":[],"responses":{"200":{"description":"An OpenAPI 3.1 document.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/api/v1/psp-webhooks/{pspId}":{"post":{"tags":["Providers"],"summary":"Inbound provider webhook","description":"Called by a payment provider, not by you. Requires an HMAC signature issued to that provider. Documented so integrators understand the inbound path, not so they call it.","operationId":"providerWebhook","security":[],"parameters":[{"name":"pspId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Payshen-Signature","in":"header","required":true,"schema":{"type":"string"},"description":"`t=<unix>,v1=HMAC_SHA256(secret, \"<t>.<body>\")`."}],"responses":{"200":{"description":"Accepted, stored and applied."},"401":{"description":"Missing or invalid signature. An unknown provider id answers identically, by design."},"429":{"description":"Rate limited."}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"`ps_test_...` for sandbox, `ps_live_...` for production."}},"parameters":{"PaymentId":{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The payment id returned as `payment_id` on creation."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable machine-readable code. Switch on this, never on the message."},"message":{"type":"string","description":"Human-readable explanation. May change; not part of the contract."},"excluded":{"type":"array","items":{"$ref":"#/components/schemas/RoutingExclusion"},"description":"Only on `routing_failed` when the request set `debug: true`."}}}}},"PaymentStatus":{"type":"string","enum":["CREATED","PENDING","REQUIRES_ACTION","AUTHORIZED","CAPTURED","DECLINED","FAILED","UNKNOWN","CANCELLED","REFUNDED","PARTIALLY_REFUNDED","CHARGEBACK","SETTLED"],"description":"The unified status set, UPPERCASE on the wire.\n\n`UNKNOWN`: the provider may have executed the operation and we could not tell (a\ntimeout after the request was sent, a connection lost mid-answer). It appears after a\ncapture or void whose provider call did not answer, and, since 2026-09-15, on the\nsynchronous branch of `POST /v1/payments` when the charge got no usable answer (with\n`decline_reason: null`, one attempt, no other provider tried, and a `payment.unknown`\nwebhook). Do not create a second payment for the same order. Payshen asks the\nprovider about it every 30 minutes and moves it to the status the provider states;\nwhat the provider cannot answer goes to Payshen operations, who settle it from the\nprovider's own record. Treat\n`UNKNOWN` like `PENDING` for display and never as a decline, and do not retry the\ncapture or void yourself. The value was added 2026-09-14 as an additive enum value\nunder the v1 freeze: no existing value was removed or changed meaning, but clients\nwith exhaustive switches must add a case."},"CreatePayment":{"type":"object","required":["amount","currency","country"],"properties":{"amount":{"type":"integer","minimum":50,"maximum":100000000,"description":"Minor units of `currency`. 5000 is EUR 50.00, and JPY 5,000."},"currency":{"type":"string","pattern":"^[A-Z]{3}$","description":"ISO 4217."},"country":{"type":"string","pattern":"^[A-Z]{2}$","description":"ISO 3166-1 alpha-2 of the customer."},"method":{"type":"string","enum":["card","bank","wallet","crypto"],"default":"card"},"card_brand":{"type":"string","maxLength":20},"reference":{"type":"string","maxLength":64,"description":"Your own identifier. Echoed back and in webhooks."},"initiator":{"type":"string","enum":["cit","mit"],"default":"cit","description":"`mit` is merchant-initiated, e.g. recurring."},"capture_method":{"type":"string","enum":["automatic","manual"],"default":"automatic"},"return_url":{"type":"string","format":"uri","description":"Present to use the hosted redirect flow."},"cancel_url":{"type":"string","format":"uri"},"debug":{"type":"boolean","default":false,"description":"When true, `routing.excluded` lists every connection removed before routing, with a `code` and a `reason`, and a `routing_failed` error carries the same list. Off by default: the list names your connections and their approval terms. Ignored on the hosted redirect branch, which does not route until the customer acts."}}},"RoutingExclusion":{"type":"object","required":["providerId","name","code","reason"],"properties":{"providerId":{"type":"string","description":"The excluded connection's id."},"name":{"type":"string","description":"The excluded connection's name."},"code":{"type":"string","description":"Stable machine-readable reason. Currently one of CURRENCY_NOT_SUPPORTED, PAYER_COUNTRY_NOT_SUPPORTED, METHOD_NOT_SUPPORTED, MERCHANT_COUNTRY_NOT_SUPPORTED (the provider does not work with merchants registered in the entity's country), VERTICAL_NOT_SUPPORTED (the provider blocks the merchant's category), RECURRING_NOT_SUPPORTED (the connection does not take merchant-initiated payments), AMOUNT_OUT_OF_RANGE, AUTHENTICATION_REQUIRED, RESTRICTION_VIOLATED, CIRCUIT_OPEN. New codes may be added, so treat unknown values as an exclusion you cannot classify rather than an error."},"reason":{"type":"string","description":"One sentence naming what excluded it. May change; not part of the contract."}}},"RoutingDecision":{"type":"object","description":"How the payment was routed. Present on the outcome branch of payment creation.","properties":{"path":{"type":"string","enum":["engine","rule","control"]},"strategy":{"type":"string"},"mode":{"type":"string","enum":["fixed","priority","failover","auto"]},"rule":{"type":["string","null"],"description":"The matched routing rule's name, if any."},"candidates":{"type":"array","items":{"type":"object","additionalProperties":true}},"attempts":{"type":"array","items":{"type":"object","additionalProperties":true}},"excluded":{"type":"array","items":{"$ref":"#/components/schemas/RoutingExclusion"},"description":"Only when the request set `debug: true`. Every connection removed before routing; empty when none was."},"eligibility_version":{"type":["string","null"],"description":"Only when the request set `debug: true`. The eligibility ruleset that produced `excluded`."},"degraded":{"type":"boolean","description":"Only when the request set `debug: true`. True when every eligible connection had an open circuit breaker and was tried anyway."}}},"NextAction":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["NONE","REDIRECT"]},"url":{"type":"string","format":"uri","description":"Send the customer here. Present when type is REDIRECT."},"expires_at":{"type":"string","format":"date-time"}}},"Payment":{"type":"object","properties":{"id":{"type":"string"},"payment_id":{"type":"string"},"reference":{"type":"string"},"payment_status":{"$ref":"#/components/schemas/PaymentStatus"},"status":{"$ref":"#/components/schemas/PaymentStatus"},"environment":{"type":"string","enum":["sandbox","production"]},"amount":{"type":"integer"},"currency":{"type":"string"},"decline_category":{"type":["string","null"],"description":"The normalized reason of a `DECLINED` payment. `authentication_failed` with the raw code `stripe_requires_action` (the create answer's `decline_reason`, the `payment.declined` webhook's `decline_code`) means the card issuer requires the customer to complete authentication (such as 3D Secure), which a server-to-server payment cannot show: nothing was charged, Payshen does not retry it and does not cancel the provider's payment. Ask the customer to pay again through a checkout where they can authenticate."},"captured_amount":{"type":"integer","description":"Minor units captured."},"refunded_amount":{"type":"integer","description":"Minor units refunded and confirmed by the provider. A refund the provider has not confirmed is not counted here."},"refund_pending_amount":{"type":"integer","description":"Minor units reserved for refunds sent to the provider and not confirmed yet, including one whose answer never came. Not refunded, and not refundable again until each is resolved."},"next_action":{"$ref":"#/components/schemas/NextAction"},"routing":{"$ref":"#/components/schemas/RoutingDecision"}}},"PaymentEnvelope":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Payment"}}},"PaymentListEnvelope":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Payment"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}}},"Event":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","description":"e.g. `payment.captured`, `payment.declined`."},"created":{"type":"string","format":"date-time"},"data":{"type":"object","additionalProperties":true}}},"EventListEnvelope":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}}}}}}