{"components":{"schemas":{"AckEventOutputBody":{"additionalProperties":false,"properties":{"ok":{"type":"boolean"}},"required":["ok"],"type":"object"},"BalanceDTO":{"additionalProperties":false,"properties":{"available":{"description":"balance − hold. New operations are accepted only while this covers them","examples":["1175.0000"],"type":"string"},"balance":{"description":"Settled funds","examples":["1250.0000"],"type":"string"},"currency":{"examples":["USD"],"type":"string"},"hold":{"description":"Reserved by operations in progress","examples":["75.0000"],"type":"string"}},"required":["currency","balance","hold","available"],"type":"object"},"CardDTO":{"additionalProperties":false,"properties":{"balance":{"type":"string"},"balance_stale":{"description":"true when balance is a last-known figure, not a live read","type":"boolean"},"created_at":{"type":"string"},"currency":{"type":"string"},"expires_at":{"description":"Month/year as reported by the issuer","type":"string"},"holder_external_id":{"type":"string"},"id":{"type":"string"},"masked_number":{"description":"Safe to store and display","type":"string"},"payment_system":{"type":"string"},"provider_missing":{"description":"true when the issuer no longer describes this card; a live card is then reported as frozen and top-ups are refused until an operator checks it","type":"boolean"},"state":{"description":"creating | active | frozen | wait_for_close | closed | expired | failed. A card that would be active or frozen is reported as frozen while provider_missing is true; any other state is reported as it stands.","type":"string"}},"required":["id","state","holder_external_id","created_at"],"type":"object"},"CardOTPDTO":{"additionalProperties":false,"properties":{"amount":{"type":"string"},"code":{"description":"The live one-time code. Show it to the cardholder and drop it — do not store, log or cache it","type":"string"},"currency":{"type":"string"},"expires_at":{"description":"After this the code is dead; stop showing it","type":"string"},"issued_at":{"description":"When the issuer raised this challenge — order by it to find the freshest code","type":"string"},"last4":{"description":"Last four digits of the card the issuer named in the challenge","type":"string"},"merchant_name":{"type":"string"},"type":{"description":"3ds (a payment the cardholder must confirm) | tokenization (adding the card to Apple Pay / Google Pay)","type":"string"}},"required":["type","code"],"type":"object"},"CardOTPOutputBody":{"additionalProperties":false,"properties":{"items":{"items":{"$ref":"#/components/schemas/CardOTPDTO"},"type":["array","null"]}},"required":["items"],"type":"object"},"CardholderDTO":{"additionalProperties":false,"properties":{"created_at":{"type":"string"},"email":{"type":"string"},"external_id":{"description":"Your identifier, echoed back. This is what you pass as holder_external_id when issuing a card.","type":"string"},"first_name":{"type":"string"},"id":{"description":"Our identifier for this holder. Informational: store your own external_id instead — it is the only value this endpoint deduplicates on, so re-registering a person by this id would create a SECOND holder.","type":"string"},"last_name":{"type":"string"}},"required":["id","external_id","first_name","last_name","email","created_at"],"type":"object"},"CreateCardholderInputBody":{"additionalProperties":false,"properties":{"email":{"description":"Contact address for this holder","maxLength":254,"type":"string"},"external_id":{"description":"Your own identifier for this person. Reuse it for repeat issuance; a new value creates a new holder and a new identity check.","maxLength":128,"type":"string"},"first_name":{"description":"Given name, as it should appear on the card","maxLength":64,"type":"string"},"last_name":{"description":"Family name, as it should appear on the card","maxLength":64,"type":"string"}},"required":["external_id","first_name","last_name","email"],"type":"object"},"CreateWebhookInputBody":{"additionalProperties":false,"properties":{"event_types":{"description":"Omit or leave empty to receive every type. An unknown type is refused rather than silently never delivered.","items":{"type":"string"},"type":["array","null"]},"url":{"description":"HTTPS endpoint. Plain HTTP is refused: the body carries operation and card ids, and a signature proves who sent it, not who can read it.","type":"string"}},"required":["url"],"type":"object"},"CreatedWebhookDTO":{"additionalProperties":false,"properties":{"secret":{"type":"string"},"warning":{"type":"string"},"webhook":{"$ref":"#/components/schemas/WebhookDTO"}},"required":["webhook","secret","warning"],"type":"object"},"DeliveryDTO":{"additionalProperties":false,"properties":{"attempts":{"format":"int64","type":"integer"},"created_at":{"type":"string"},"delivered_at":{"type":"string"},"event_id":{"type":"string"},"id":{"type":"string"},"last_error":{"description":"Our classification and the response code — never your response body","type":"string"},"last_status_code":{"format":"int64","type":"integer"},"next_attempt_at":{"type":"string"},"status":{"description":"pending | delivered | failed","type":"string"}},"required":["id","event_id","status","attempts","created_at"],"type":"object"},"ErrorDetail":{"additionalProperties":false,"properties":{"location":{"description":"Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'","type":"string"},"message":{"description":"Error message text","type":"string"},"value":{"description":"The value at the given location"}},"type":"object"},"ErrorModel":{"additionalProperties":false,"properties":{"detail":{"description":"A human-readable explanation specific to this occurrence of the problem.","examples":["Property foo is required but is missing."],"type":"string"},"errors":{"description":"Optional list of individual error details","items":{"$ref":"#/components/schemas/ErrorDetail"},"type":["array","null"]},"instance":{"description":"A URI reference that identifies the specific occurrence of the problem.","examples":["https://example.com/error-log/abc123"],"format":"uri","type":"string"},"status":{"description":"HTTP status code","examples":[400],"format":"int64","type":"integer"},"title":{"description":"A short, human-readable summary of the problem type. This value should not change between occurrences of the error.","examples":["Bad Request"],"type":"string"},"type":{"default":"about:blank","description":"A URI reference to human-readable documentation for the error.","examples":["https://example.com/errors/example"],"format":"uri","type":"string"}},"type":"object"},"EventDTO":{"additionalProperties":false,"properties":{"card_id":{"type":"string"},"created_at":{"type":"string"},"delivery_count":{"format":"int64","type":"integer"},"id":{"description":"Pass this to the ack endpoint once you have processed the event","type":"string"},"operation_id":{"description":"The operation this event concerns — use it to deduplicate","type":"string"},"payload":{"additionalProperties":{},"type":"object"},"type":{"description":"card.issued | card.issue_failed | card.topped_up | card.topup_failed | card.closed | card.balance_reclaimed | operation.needs_review","type":"string"}},"required":["id","type","payload","created_at","delivery_count"],"type":"object"},"IssueCardInputBody":{"additionalProperties":false,"properties":{"amount":{"description":"Initial funding, USD decimal string. Omit or \"0\" to issue an empty card.","examples":["50.00"],"type":"string"},"amount_type":{"description":"credit (default) = amount lands on the card, we compute what you are charged. debit = amount is taken from your balance, we compute what lands on the card.","enum":["credit","debit"],"type":"string"},"card_name":{"maxLength":64,"type":"string"},"holder_external_id":{"description":"The external_id you registered via POST /cardholders. An integration that stored our id for that holder instead still resolves here, but external_id remains the key to keep: it is the one POST /cardholders deduplicates on.","maxLength":128,"type":"string"},"tariff_id":{"description":"From GET /tariffs","format":"uuid","type":"string"}},"required":["tariff_id","holder_external_id"],"type":"object"},"ListDeliveriesOutputBody":{"additionalProperties":false,"properties":{"items":{"items":{"$ref":"#/components/schemas/DeliveryDTO"},"type":["array","null"]}},"required":["items"],"type":"object"},"ListTariffsOutputBody":{"additionalProperties":false,"properties":{"items":{"items":{"$ref":"#/components/schemas/TariffDTO"},"type":["array","null"]}},"required":["items"],"type":"object"},"ListTxOutputBody":{"additionalProperties":false,"properties":{"items":{"items":{"$ref":"#/components/schemas/TransactionDTO"},"type":["array","null"]}},"required":["items"],"type":"object"},"ListWebhooksOutputBody":{"additionalProperties":false,"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookDTO"},"type":["array","null"]}},"required":["items"],"type":"object"},"OkOutputBody":{"additionalProperties":false,"properties":{"ok":{"type":"boolean"}},"required":["ok"],"type":"object"},"OperationDTO":{"additionalProperties":false,"properties":{"card_id":{"type":"string"},"created_at":{"type":"string"},"error_code":{"description":"tariff_not_available (the tariff cannot be issued on — retrying will not help) | maintenance (we closed this flow; retry later) | provider_unavailable (we could not reach the card issuer; no money moved, safe to retry) | provider_rejected (the issuer refused the request itself) | order_expired | refunded | needs_review (funds reached the issuer but no card appeared — DO NOT retry, we are resolving it) | unknown_order_status | issue_not_started (issuance never began; unclassified)","type":"string"},"hold_amount":{"description":"Reserved on your balance while the operation is in flight","type":"string"},"id":{"type":"string"},"kind":{"description":"issue | topup | close | cashout","type":"string"},"net_amount":{"description":"Credited to the card (or returned from it, for a close)","type":"string"},"refund":{"$ref":"#/components/schemas/RefundDTO","description":"Close only: the card's remaining balance returned to you"},"settled_at":{"type":"string"},"status":{"description":"processing | succeeded | failed | needs_review","type":"string"}},"required":["id","kind","status","hold_amount","net_amount","created_at"],"type":"object"},"PollEventsOutputBody":{"additionalProperties":false,"properties":{"items":{"items":{"$ref":"#/components/schemas/EventDTO"},"type":["array","null"]}},"required":["items"],"type":"object"},"QuoteInputBody":{"additionalProperties":false,"properties":{"amount":{"examples":["50.00"],"type":"string"},"amount_type":{"enum":["credit","debit"],"type":"string"},"kind":{"description":"What to price. Default: issue.","enum":["issue","topup","close"],"type":"string"},"tariff_id":{"format":"uuid","type":"string"}},"required":["tariff_id"],"type":"object"},"QuoteOutputBody":{"additionalProperties":false,"properties":{"close_fee":{"description":"Flat closure charge included in debit","type":"string"},"credit":{"description":"What reaches the card","type":"string"},"debit":{"description":"Total taken from your balance","type":"string"},"issue_fee":{"description":"Flat issuance charge included in debit","type":"string"},"topup_fee":{"description":"Markup included in debit","type":"string"}},"required":["debit","credit","issue_fee","close_fee","topup_fee"],"type":"object"},"RefundDTO":{"additionalProperties":false,"properties":{"amount":{"description":"Credited to your balance: residual − fee","type":"string"},"fee":{"description":"Kept under the tariff's close_refund_fee_pct","type":"string"},"residual":{"description":"What the card held when it was closed","type":"string"},"status":{"description":"refunded | zero (the card was empty) | unknown (balance unreadable; nothing credited yet, settled manually)","type":"string"}},"required":["status"],"type":"object"},"SecretOutputBody":{"additionalProperties":false,"properties":{"cvv":{"type":"string"},"expiry_month":{"type":"string"},"expiry_year":{"type":"string"},"masked_number":{"type":"string"},"pan":{"type":"string"}},"required":["pan","cvv","expiry_month","expiry_year"],"type":"object"},"SetWebhookActiveInputBody":{"additionalProperties":false,"properties":{"is_active":{"type":"boolean"}},"required":["is_active"],"type":"object"},"TariffDTO":{"additionalProperties":false,"properties":{"close_fee":{"description":"Charge applied when the card is closed, USD","examples":["0.50"],"type":"string"},"close_refund_fee_pct":{"description":"Percent kept from a closed card's remaining balance; the rest is credited back to your balance. 0 = returned in full","examples":["0.0000"],"type":"string"},"description":{"description":"Free-form description","type":"string"},"id":{"description":"Tariff identifier — pass this to POST /cards","type":"string"},"issue_fee":{"description":"One-off issuance charge, USD","examples":["3.00"],"type":"string"},"max_topup":{"description":"Largest amount that may be credited to a card under this tariff, USD. Absent means no maximum","examples":["500.00"],"type":"string"},"min_topup":{"description":"Smallest amount that may be credited to a card under this tariff, USD. Absent means no minimum","examples":["1.00"],"type":"string"},"name":{"description":"Product name","type":"string"},"topup_fee_const":{"description":"Fixed part of the top-up markup, USD","examples":["0.30"],"type":"string"},"topup_fee_pct":{"description":"Top-up markup in percent. You are debited gross; the card is credited net","examples":["5.0000"],"type":"string"},"tx_fee_declined":{"description":"Charged per declined card transaction, USD","examples":["0.20"],"type":"string"},"tx_fee_ok":{"description":"Charged per approved card transaction, USD","examples":["0.10"],"type":"string"}},"required":["id","name","issue_fee","close_fee","close_refund_fee_pct","topup_fee_pct","topup_fee_const","tx_fee_ok","tx_fee_declined"],"type":"object"},"TopupInputBody":{"additionalProperties":false,"properties":{"amount":{"examples":["25.00"],"type":"string"},"amount_type":{"description":"credit (default) = amount lands on the card. debit = amount leaves your balance.","enum":["credit","debit"],"type":"string"}},"required":["amount"],"type":"object"},"TransactionDTO":{"additionalProperties":false,"properties":{"amount":{"type":"string"},"currency":{"type":"string"},"decline_reason":{"type":"string"},"fee":{"type":"string"},"id":{"type":"string"},"mcc":{"type":"string"},"merchant_country":{"type":"string"},"merchant_name":{"type":"string"},"occurred_at":{"type":"string"},"status":{"type":"string"},"type":{"description":"purchase | refund | top_up | withdrawal | fee","type":"string"}},"required":["id","type","status","amount","currency"],"type":"object"},"WebhookDTO":{"additionalProperties":false,"properties":{"created_at":{"type":"string"},"event_types":{"description":"Empty means every event type","items":{"type":"string"},"type":["array","null"]},"id":{"type":"string"},"is_active":{"type":"boolean"},"secret_last4":{"type":"string"},"url":{"type":"string"}},"required":["id","url","secret_last4","event_types","is_active","created_at"],"type":"object"}},"securitySchemes":{"hmac":{"description":"Every request is signed with HMAC-SHA256.\n\nHeaders:\n  X-Key-Id      your key identifier (pp_live_… or pp_test_…)\n  X-Timestamp   current unix time in SECONDS\n  X-Signature   lowercase hex HMAC-SHA256 of the string below, keyed by your secret\n\nString to sign — parts joined by a newline (\\n), in this exact order:\n\n  \u003ctimestamp\u003e\\n\u003cMETHOD\u003e\\n\u003cpath including query string\u003e\\n\u003craw request body\u003e\n\nWhen you send an Idempotency-Key, APPEND IT as a fifth part:\n\n  \u003ctimestamp\u003e\\n\u003cMETHOD\u003e\\n\u003cpath\u003e\\n\u003cbody\u003e\\n\u003cIdempotency-Key\u003e\n\nNotes:\n  * METHOD is upper-case (POST, GET, …).\n  * The path is what follows the host, query string included, e.g.\n    /api/partner/v1/events?limit=10\n  * The body is the raw bytes you send. For requests without a body, use an\n    empty string.\n  * The timestamp must be within 5 minutes of our clock, so keep NTP running.\n  * A signature may be used once. Retrying an identical request needs a new\n    timestamp and therefore a new signature.\n  * Both forms are accepted, but on requests that carry an Idempotency-Key the\n    five-part form is the one to use, and the four-part form has a failure mode\n    you will hit in production. The timestamp is in SECONDS, so two money calls\n    issued in the same second — a retry of one top-up while another one\n    starts — have identical timestamp, method, path and body. Without the key in\n    the string they hash to ONE signature, and the second call is refused as\n    partner_replay even though it is a different operation. Including the key\n    makes the signature per-operation and the collision impossible.\n\nExample (bash):\n\n  TS=$(date +%s)\n  BODY='{\"external_id\":\"cust-42\"}'\n  IDEM=$(uuidgen)\n  # awk '{print $NF}' — the LAST field. Older openssl prints\n  # \"SHA2-256(stdin)= \u003chex\u003e\", newer prints the bare hex; $NF is the digest in\n  # both, while $2 silently yields an empty signature on the newer form.\n  #\n  # $IDEM is the fifth part BECAUSE the request below sends that header. Sign\n  # what you send: drop it here while keeping the header, and two calls made in\n  # the same second collide into one signature (see the note above).\n  SIG=$(printf '%s\\nPOST\\n/api/partner/v1/cardholders\\n%s\\n%s' \"$TS\" \"$BODY\" \"$IDEM\" \\\n        | openssl dgst -sha256 -hmac \"$SECRET\" -hex | awk '{print $NF}')\n  curl -X POST https://api.plativputi.com/api/partner/v1/cardholders \\\n    -H \"X-Key-Id: $KEY_ID\" -H \"X-Timestamp: $TS\" -H \"X-Signature: $SIG\" \\\n    -H 'Content-Type: application/json' -H \"Idempotency-Key: $IDEM\" \\\n    -d \"$BODY\"\n","in":"header","name":"X-Key-Id","type":"apiKey"}}},"info":{"title":"Plati v Puti — Partner Cards API","version":"1.0.0"},"openapi":"3.1.0","paths":{"/balance":{"get":{"description":"Returns your prepaid USD balance.\n\n`available` is the figure that matters: issuing and topping up are refused with 402 `balance_exhausted` once it no longer covers the operation, even if `balance` still looks healthy — the difference is held against work already in flight.\n\n`balance` can go negative. Per-transaction fees are charged after the card has already paid a merchant, so they cannot be refused. Cards already issued keep working; only new operations are blocked until you top up.","operationId":"get-balance","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BalanceDTO"}}},"description":"OK"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Get account balance","tags":["Balance"]}},"/cardholders":{"post":{"description":"Registers the person a card will be issued to, or returns the existing holder when `external_id` has been seen before.\n\n**Reuse `external_id` for the same person.** The first card for a new holder waits on an identity check at the card issuer; later cards for the same holder skip it. Sending a fresh `external_id` per card pays that wait every time and registers the same person repeatedly.\n\nName and email are used as the cardholder identity. The remaining identity data the issuer requires is generated on our side.\n\nCalling this again with the same `external_id` and DIFFERENT names returns the original holder unchanged — by then that name is already registered with the issuer and printed on issued cards.","operationId":"create-cardholder","parameters":[{"description":"Optional here — this endpoint is naturally idempotent on external_id","in":"header","name":"Idempotency-Key","schema":{"description":"Optional here — this endpoint is naturally idempotent on external_id","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCardholderInputBody"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardholderDTO"}}},"description":"Created"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Register a cardholder","tags":["Cardholders"]}},"/cardholders/{external_id}":{"get":{"description":"Returns the holder registered under your `external_id`. Use it to check whether a person is already registered before issuing.\n\nAn integration that stored the `id` we minted resolves here too. That is compatibility, not the contract: `external_id` is the key `POST /cardholders` deduplicates on, so it is the one to keep.","operationId":"get-cardholder","parameters":[{"description":"Your identifier for this person. The id we returned for that holder resolves here too.","in":"path","name":"external_id","required":true,"schema":{"description":"Your identifier for this person. The id we returned for that holder resolves here too.","maxLength":128,"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardholderDTO"}}},"description":"OK"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Look up a cardholder","tags":["Cardholders"]}},"/cards":{"post":{"description":"Reserves the funds and starts issuance.\n\n**The response is an operation, not a card.** Issuance is asynchronous: the card is created at the issuer over the following seconds. Wait for the `card.issued` event or poll `GET /operations/{id}` until `status` leaves `processing`.\n\nYour balance is held, not charged, until the operation settles. A failure releases the hold in full.\n\n`Idempotency-Key` is required. Retrying with the same key returns the original operation — it will never issue a second card.\n\nThe **first** card for a newly registered holder waits on an identity check at the issuer and takes noticeably longer than subsequent ones.","operationId":"issue-card","parameters":[{"description":"Required. A retry with the same key returns the original operation instead of issuing a second card.","in":"header","name":"Idempotency-Key","required":true,"schema":{"description":"Required. A retry with the same key returns the original operation instead of issuing a second card.","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueCardInputBody"}}},"required":true},"responses":{"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationDTO"}}},"description":"Accepted"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Issue a card","tags":["Cards"]}},"/cards/{id}":{"get":{"description":"Returns one of your cards with its current balance, read live from the issuer.\n\n`balance` is omitted when the issuer could not be reached — treat an absent balance as unknown, never as zero. When `balance_stale` is true the figure is the last one we recorded rather than a live read; it is still a real number, but do not size an irreversible operation on it.\n\n`provider_missing: true` means the issuer has stopped describing this card — it was most likely closed or removed on their side. We do not know that it is closed, so we do not report it as closed: a card that would be `active` or `frozen` is reported as `frozen`, `balance` is omitted, and top-ups are refused (`card_not_active`) until an operator has checked it with the issuer. Treat the card as unusable and stop quoting its balance. Closing it is still allowed — that is how the case is resolved — and `/secret` still serves credentials, since the holder may need them for a dispute. If the issuer answers again the flag clears by itself and the card returns to its real state.\n\nCard credentials are NOT here. They are served only by `GET /cards/{id}/secret`, and only if your account is enabled for it.","operationId":"get-card","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardDTO"}}},"description":"OK"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Get a card","tags":["Cards"]}},"/cards/{id}/close":{"post":{"description":"Closes the card and charges the tariff's close fee.\n\nUnlike issuing and topping up, this settles **synchronously**: the operation comes back already `succeeded` or `failed`, and the fee is charged only if the closure actually happened.\n\n**Whatever was left on the card is credited back to your balance** in the same call, less the tariff's `close_refund_fee_pct` (0 by default — returned in full). The response carries it in `refund`, and a `card.balance_reclaimed` event follows. `refund.status` is `refunded`, `zero` (the card was empty) or `unknown` — the balance could not be read, nothing was credited yet, and we settle it by hand.\n\nClosing an already-closed card is a no-op success.","operationId":"close-card","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"header","name":"Idempotency-Key","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationDTO"}}},"description":"OK"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Close a card","tags":["Cards"]}},"/cards/{id}/otp":{"get":{"description":"The one-time codes the issuer is currently asking the cardholder for: a 3-D Secure challenge on a payment, or the provisioning challenge Apple Pay and Google Pay raise when the card is added to a wallet.\n\n**Poll it.** A code exists for about two minutes and there is no push channel for it — nothing tells you one has appeared, so ask while your customer is on a confirmation screen. Repeated calls return the same codes until they expire; polling one card no more often than once every few seconds is enough, and a card with nothing pending answers `200` with an empty list.\n\nNot every card can produce a code: it depends on the issuer behind the tariff. A card whose issuer has no such channel simply never returns one.\n\n**The codes are live secrets.** Show them to the cardholder and drop them — do not store, log or cache them. The response is marked `no-store` for the same reason.","operationId":"list-card-otp","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardOTPOutputBody"}}},"description":"OK","headers":{"Cache-Control":{"schema":{"type":"string"}},"Pragma":{"schema":{"type":"string"}}}},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Get pending payment-confirmation codes","tags":["Cards"]}},"/cards/{id}/secret":{"get":{"description":"Returns the full PAN, CVV and expiry.\n\n**This puts you inside PCI scope.** Do not store, log or cache the response; pass it straight to the cardholder and drop it. The response is marked `no-store` for the same reason.\n\nTwo conditions must both hold: your key carries the `cards:secret` scope, and your account is enabled for raw details. Missing either gives 403 `card_secret_not_allowed`.\n\nTreat a key with this scope as the most sensitive credential you hold: keep its secret in a secret manager, use a dedicated key for it rather than the one that issues cards, and rotate it on any suspicion.\n\nThis endpoint has its own, much tighter rate limit than the rest of the API, and every call is recorded.","operationId":"get-card-secret","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecretOutputBody"}}},"description":"OK","headers":{"Cache-Control":{"schema":{"type":"string"}},"Pragma":{"schema":{"type":"string"}}}},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Get card credentials","tags":["Cards"]}},"/cards/{id}/topup":{"post":{"description":"Reserves the funds and starts a top-up. Like issuance, this is **asynchronous**: the response is an operation, not a new balance. Wait for `card.topped_up` or poll `GET /operations/{id}`.\n\nPricing uses the tariff the card was ISSUED under, not one you name — so the cost of topping a card up never depends on which tariff you quote against.\n\nOnly an `active` card can be topped up. A card that is still being created is refused with `card_not_active` — wait for `card.issued` first.","operationId":"topup-card","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Required. A retry with the same key returns the original operation.","in":"header","name":"Idempotency-Key","required":true,"schema":{"description":"Required. A retry with the same key returns the original operation.","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupInputBody"}}},"required":true},"responses":{"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationDTO"}}},"description":"Accepted"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Top up a card","tags":["Cards"]}},"/cards/{id}/transactions":{"get":{"description":"The card's statement, read from the issuer.\n\n`fee` is what the issuer charged for that transaction and is reported when known — it is not always available.\n\nWhen `GET /cards/{id}` reports `provider_missing: true` this is the statement we last recorded, not a live one: the issuer is not describing the card, so there is nothing new to read from it.\n\nThis is the card's own spending history. It is not your billing: what you were charged is in your operations and your balance ledger.","operationId":"list-card-transactions","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"1-based page number","explode":false,"in":"query","name":"page","schema":{"default":1,"description":"1-based page number","format":"int64","minimum":1,"type":"integer"}},{"explode":false,"in":"query","name":"size","schema":{"default":50,"format":"int64","maximum":200,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListTxOutputBody"}}},"description":"OK"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"List card transactions","tags":["Cards"]}},"/events":{"get":{"description":"Returns unacknowledged events, oldest first.\n\nDelivery is **at-least-once**: an event stays queued until you acknowledge it, so a crash mid-processing means you will see it again. Make your handlers idempotent and key them on `operation_id`.\n\nAcknowledge each event with `POST /events/{id}/ack` after processing. An event you keep fetching without acknowledging will keep coming back, and its `delivery_count` will tell you so.","operationId":"poll-events","parameters":[{"description":"How many events to fetch","explode":false,"in":"query","name":"limit","schema":{"default":20,"description":"How many events to fetch","format":"int64","maximum":100,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PollEventsOutputBody"}}},"description":"OK"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Fetch pending events","tags":["Events"]}},"/events/{id}/ack":{"post":{"description":"Removes the event from your queue. Idempotent — acknowledging an already-acknowledged event succeeds, so it is safe to retry when you are unsure the first call landed.","operationId":"ack-event","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AckEventOutputBody"}}},"description":"OK"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Acknowledge an event","tags":["Events"]}},"/operations/{id}":{"get":{"description":"The reconciliation path: poll this when you have not received an event, or to verify state after a restart.\n\n`status` values:\n* `processing` — in flight; your funds are held.\n* `succeeded` — done; your balance has been charged.\n* `failed` — nothing was charged; the hold was released. Safe to retry.\n* `needs_review` — stopped in a state we will not resolve automatically. Your funds remain held and we are looking at it. **Do not retry**: the operation may in fact have completed.","operationId":"get-operation","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationDTO"}}},"description":"OK"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Get operation status","tags":["Operations"]}},"/quote":{"post":{"description":"Computes what an operation would cost. No side effects — nothing is reserved and no card is touched.\n\nUse `amount_type` to choose which side you name. `credit` (default): you say what should land on the card and we tell you what you are charged. `debit`: you name a budget and we tell you what lands on the card.\n\nRounding is to 4 decimal places and always in our favour by at most 0.0001 USD, so a quote is never an underestimate of what you will pay.","operationId":"quote","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteInputBody"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteOutputBody"}}},"description":"OK"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Price an operation","tags":["Cards"]}},"/tariffs":{"get":{"description":"Returns the tariffs enabled for your account. A tariff that disappears from this list has been retired and can no longer be used for new cards; cards already issued on it are unaffected, because their price was fixed at issuance.","operationId":"list-tariffs","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListTariffsOutputBody"}}},"description":"OK"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"List available tariffs","tags":["Tariffs"]}},"/webhooks":{"get":{"operationId":"list-webhooks","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListWebhooksOutputBody"}}},"description":"OK"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"List webhook subscriptions","tags":["Webhooks"]},"post":{"description":"Registers an HTTPS endpoint and returns its signing secret.\n\nThe secret is shown ONCE. Every delivery carries `X-Timestamp` and `X-Signature`, where the signature is `hex(HMAC-SHA256(secret, timestamp \\n POST \\n path \\n body))` — the same construction you use to sign requests to us, so one implementation covers both directions. `path` follows the same rule in both directions: what comes after the host, query string included, so a subscription URL carrying `?tenant=42` is signed with it.\n\nAnswer 2xx to acknowledge. Anything else is retried with backoff (1m, 5m, 15m, 30m, 60m) and then given up on.","operationId":"create-webhook","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookInputBody"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedWebhookDTO"}}},"description":"Created"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Create a webhook subscription","tags":["Webhooks"]}},"/webhooks/{id}":{"delete":{"description":"Removes the subscription and its delivery journal. Events already queued for it are dropped; the poll queue is unaffected.","operationId":"delete-webhook","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OkOutputBody"}}},"description":"OK"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Delete a subscription","tags":["Webhooks"]},"patch":{"operationId":"set-webhook-active","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetWebhookActiveInputBody"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OkOutputBody"}}},"description":"OK"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Enable or disable a subscription","tags":["Webhooks"]}},"/webhooks/{id}/deliveries":{"get":{"description":"What we tried to send you and what happened. Use it to diagnose a missing event without asking support.","operationId":"list-webhook-deliveries","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"explode":false,"in":"query","name":"limit","schema":{"default":50,"format":"int64","maximum":200,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListDeliveriesOutputBody"}}},"description":"OK"},"default":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorModel"}}},"description":"Error"}},"summary":"Delivery journal for a subscription","tags":["Webhooks"]}}},"security":[{"hmac":[]}],"servers":[{"url":"/api/partner/v1"}]}