# AletaPlanet API — endpoint digest > Generated from the OpenAPI specification at build time. Do not edit by hand. > Version: 1.0.0 > Full specification: https://developer.aletaplanet.com/openapi.yaml > Prose documentation: https://developer.aletaplanet.com/llms-full.txt Issue virtual cards, hold value in accounts, and authorize transactions in real time. New to the platform? Start with [core concepts](https://developer.aletaplanet.com/docs/products/card-issuing/core-concepts) — the object model everything here assumes. ## Not every program has every endpoint A program is bound to a card processor, and processors do not cover the same ground. An operation the one behind your programs cannot perform is **not mounted**: the path returns `404` with `not_found`, in the same envelope as a misspelled URL. That is deliberate. An endpoint that existed and always failed would read as a service that is nearly finished, and a `502` would tell you the processor was unavailable when in fact the operation does not exist for you at all. Operations that depend on a capability carry `x-aleta-capability` below. Probe for a `404` at integration time rather than at run time — the set does not change without notice, and probing is the answer that cannot be stale. ## Servers - https://api.aletaplanet.com (Both environments. Your access token decides which one a request runs against — `at_test_` for sandbox, `at_live_` for production.) ## Conventions - Authentication: `Authorization: Bearer ` on every request. - Mutating calls accept `Idempotency-Key`. Generate it once, reuse across retries. - Money is `{ "amount": , "currency": "" }`. Never a float. - Timestamps are RFC 3339 UTC, e.g. `2026-07-29T14:32:05Z`. - Identifiers are opaque and prefixed (`card_`, `chd_`, `acct_`, `auth_`, `txn_`). - Every failure returns `{ "error": { type, code, message, request_id, ... } }`. - Lists are cursor-paginated: loop while `has_more`, passing `next_cursor` as `starting_after`. ## Endpoints ### Authentication Obtaining the access token every other operation requires. One endpoint, reached without a token. #### POST /oauth/token operationId: issueAccessToken summary: Issue an access token Exchanges a signed client assertion for an opaque access token. The assertion is a JWT signed with your private key, carrying: | Claim | Value | | --- | --- | | `iss`, `sub` | Your client id (`oc_…`), both | | `aud` | This endpoint's absolute URL for the environment you are calling | | `exp` | At most five minutes ahead | | `jti` | A value unique among your own assertions; each is redeemable once | Signed with `ES256` or `RS256`. Every registered key must carry a `kid` once a client holds more than one; the JWT header's own `kid` picks which one verifies the assertion, and omitting it tries them all. Send it anyway — it makes a rotation legible in your own logs — but a refusal will not name the key: every pre-verification failure, clock skew included, returns the same deliberately generic `invalid_client` so that an unauthenticated caller cannot learn whether a client id is real. Omit `scope` to receive every scope the client holds. Name a scope the client does not hold and the whole request is refused. **This endpoint answers in RFC 6749's error shape, not the envelope the rest of the API uses.** Failures are `{"error": "…", "error_description": "…"}`, flat, because that is what OAuth client libraries parse. Nothing here is retryable without changing the request — a fresh assertion, a corrected audience, or a scope the client actually holds. Responses: 200 Token issued. Not cacheable. 400 Malformed request, unsupported grant, an assertion already 401 The client is unknown or revoked, the assertion did not verify, 500 An unexpected failure on our side, unrelated to the request. ### Programs The card products your organization may issue against. A program is operated by AletaPlanet and attached to your organization; you read the catalogue, you do not create rows in it. Everything you create belongs to exactly one program. An account names one when it is opened and everything that account owns inherits it, so the `prog_` id is the first value an integration needs. #### GET /v1/programs operationId: listPrograms summary: List your programs Every program your organization can issue against in this environment. A program's ID is different in sandbox and production, so read it here instead of hard-coding it. Your access token determines the organization and environment, so you don't need to pass anything else. Use your production credentials to get your production program IDs. The same products will have different `prog_` IDs. Responses: 200 Every program attached to your organization in this environment. 401 Credentials are missing, invalid, or expired. Do not retry. 429 Rate limit exceeded. Honor `Retry-After`. ### Cardholders The person a card belongs to. Every account and card references one. Identity verification state gates card issuance. #### GET /v1/cardholders operationId: listCardholders summary: List cardholders Returns a page of cardholders, most recently created first. Cursor-paginated: loop while `has_more`, passing `next_cursor` as `starting_after`. Parameters: limit (in: query, integer) — Page size. Values above the maximum are rejected, not clamped. starting_after (in: query, string) — Opaque cursor for the next page. Valid for 24 hours. ending_before (in: query, string) — Opaque cursor for the previous page. Mutually exclusive with `starting_after`. kyc_status (in: query, enum(not_started | pending | approved | rejected | review_required)) — Filter by verification state. reference (in: query, string) — Filter by your own identifier. Responses: 200 A page of cardholders. 400 The request is malformed or contains an unrecognized parameter. 401 Credentials are missing, invalid, or expired. Do not retry. 429 Rate limit exceeded. Honor `Retry-After`. #### POST /v1/cardholders operationId: createCardholder summary: Create a cardholder Creates the cardholder record that accounts and cards belong to. A new cardholder starts with a `kyc_status` of `not_started` or `pending`, depending on the program. A card can't be issued until the status reaches `approved`. Only individual cardholders are supported. Business cardholders will be supported in the future. Parameters: Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: type (enum(individual)) REQUIRED — The kind of cardholder to create. Only `individual` is supported for now. first_name (string) REQUIRED last_name (string) REQUIRED email (string) phone (string(^\+[1-9]\d{6,14}$)) date_of_birth (string) address (object) — Optional here, but required before a card can be issued to this reference (string) identity_document (object) — Optional here, but required before a card can be issued to this Responses: 201 Cardholder created. 400 The request is malformed or contains an unrecognized parameter. 401 Credentials are missing, invalid, or expired. Do not retry. 403 Credentials are valid but not permitted. Do not retry. 409 Idempotency key reused with a different body, or a concurrent 422 Well-formed but fails a business rule. 429 Rate limit exceeded. Honor `Retry-After`. #### GET /v1/cardholders/{cardholder_id} operationId: getCardholder summary: Retrieve a cardholder Returns a single cardholder, including its current verification state. Parameters: cardholder_id (in: path, string(^chd_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Responses: 200 The cardholder. 401 Credentials are missing, invalid, or expired. Do not retry. 404 The resource does not exist, or belongs to another organization. The two #### PATCH /v1/cardholders/{cardholder_id} operationId: updateCardholder summary: Update a cardholder Only the fields supplied are changed. Parameters: cardholder_id (in: path, string(^chd_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: email (string) phone (string(^\+[1-9]\d{6,14}$)) address (object) address.line1 (string) REQUIRED address.line2 (string) address.city (string) REQUIRED address.state (string) address.postal_code (string) REQUIRED address.country (string(^[A-Z]{3}$)) REQUIRED — Uppercase ISO 3166-1 alpha-3 code. reference (string) Responses: 200 The updated cardholder. 400 The request is malformed or contains an unrecognized parameter. 404 The resource does not exist, or belongs to another organization. The two 422 Well-formed but fails a business rule. #### GET /v1/cardholders/{cardholder_id}/cards operationId: listCardholderCards summary: List a cardholder's cards Returns every card issued to this cardholder, across all of its accounts. Parameters: cardholder_id (in: path, string(^chd_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED limit (in: query, integer) — Page size. Values above the maximum are rejected, not clamped. starting_after (in: query, string) — Opaque cursor for the next page. Valid for 24 hours. Responses: 200 A page of cards. 404 The resource does not exist, or belongs to another organization. The two #### GET /v1/cardholders/{cardholder_id}/kyc operationId: listKycAttestations summary: List KYC attestations Returns all identity verification attestations recorded for the cardholder, newest first. The first item is the current one. If a cardholder is verified again later, each verification is kept. This gives you the full history an auditor may need, without having to rebuild it from webhook events. Parameters: cardholder_id (in: path, string(^chd_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED limit (in: query, integer) — Page size. Values above the maximum are rejected, not clamped. starting_after (in: query, string) — Opaque cursor for the next page. Valid for 24 hours. Responses: 200 A page of attestations, newest first. 401 Credentials are missing, invalid, or expired. Do not retry. 404 The resource does not exist, or belongs to another organization. The two #### POST /v1/cardholders/{cardholder_id}/kyc operationId: reportKycOutcome summary: Report a KYC outcome Records the result of an identity check you performed and updates the cardholder's `kyc_status`. **You run the verification; we record the result.** The request is an attestation that says who verified the person, when, how, and what the result was. Because this is a compliance record, `provider` and `verified_at` are required. Reporting `approved` allows cards to be issued. If you report a new result for a cardholder who was already verified, the new attestation replaces the previous one as the current result. Both records are still kept and can be read. ### When you also send the identity and documents Usually, you only send the attestation. The identity documents stay with you and your verification provider. Some processors do their own identity checks and need the person's details and documents before opening an account. For those programs, you can send that information here: - `identity` contains the required identity fields. - `documents` contains the document images. If the program requires this evidence and you don't provide it, creating an account is rejected with `kyc_identity_required`. The attestation is still recorded normally. This is the only API request that accepts files, so it has a larger request size limit. It's designed for up to three documents, with each file up to 20 MiB, base64 encoded. Documents are stored encrypted outside the main database. We don't return the files later. The `documents` field in responses only contains metadata, with no URL or file content. Parameters: cardholder_id (in: path, string(^chd_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: status (enum(not_started | pending | approved | rejected | review_required)) REQUIRED — The outcome you are asserting. `not_started` cannot be reported — verified_at (string) REQUIRED — When you completed verification. May be in the past; must not be in provider (string) REQUIRED — The identity provider you used, or your own organization's name if provider_reference (string) — The provider's own identifier for this check. Strongly recommended: method (enum(document_and_selfie | document_only | database_check | in_person | other)) — How the cardholder's identity was established. expires_at (string) — When this verification lapses and re-verification is due. Omit if notes (string) — Free text for context. **Never put identity document contents, reference (string) — Your own identifier for this attestation. identity (object) — What the verification established about the person. documents (array) — The images supporting the verification, at most one of each `kind`. Responses: 201 The recorded attestation. 400 The request is malformed or contains an unrecognized parameter. 404 The resource does not exist, or belongs to another organization. The two 422 Well-formed but fails a business rule. ### Accounts Where value is held. One currency per account. Carries three balances — settled, held, and available. An account can be funded before any card is issued on it. The balance belongs to the account, not to a card. #### GET /v1/accounts operationId: listAccounts summary: List accounts Returns a page of accounts. Filter by `cardholder_id` to get the accounts belonging to one cardholder, or by `program_id` to get one product's accounts. Parameters: limit (in: query, integer) — Page size. Values above the maximum are rejected, not clamped. starting_after (in: query, string) — Opaque cursor for the next page. Valid for 24 hours. cardholder_id (in: query, string(^chd_[0-9A-HJKMNP-TV-Z]{26}$)) — Return only accounts belonging to this cardholder. program_id (in: query, string(^prog_[0-9A-HJKMNP-TV-Z]{26}$)) — Return only accounts opened under this program. Omit it and the page Responses: 200 A page of accounts. 400 The request is malformed or contains an unrecognized parameter. 401 Credentials are missing, invalid, or expired. Do not retry. 429 Rate limit exceeded. Honor `Retry-After`. #### POST /v1/accounts operationId: createAccount summary: Create an account Opens a stored-value account for a cardholder in one currency. If a cardholder needs two currencies, they need two separate accounts. The API never converts between currencies automatically. **This is where you set the program.** `program_id` tells us which of your products the account belongs to. Everything connected to the account — its cards, authorizations, and transactions — inherits that program. If `program_id` isn't one of your programs, the API returns `invalid_parameter`. It uses the same response whether the program doesn't exist or simply isn't yours, so the API won't reveal whether another program exists on the platform. Parameters: Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: program_id (string(^prog_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED — Which of your programs to open this account under. This is the one cardholder_id (string(^chd_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED currency (string(^[A-Z]{3}$)) REQUIRED — Uppercase ISO 4217 alphabetic code. reference (string) upstream_reference (string) — Adopts an account that already exists at the processor. Responses: 201 Account created. 400 The request is malformed or contains an unrecognized parameter. 404 `cardholder_not_found`: no cardholder with that id, or it belongs to 422 Well-formed but fails a business rule. #### GET /v1/accounts/{account_id} operationId: getAccount summary: Retrieve an account Returns a single account, including its current balances. The `ledger_balance` is the total value in the account. The `available_balance` is the value that can be spent, which is the ledger balance minus any funds held by pending authorizations. Parameters: account_id (in: path, string(^acct_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Responses: 200 The account. 404 The resource does not exist, or belongs to another organization. The two #### POST /v1/accounts/{account_id}/credit operationId: creditAccount summary: Credit an account Adds money to an account, increasing both `ledger_balance` and `available_balance`. There are two ways to prevent duplicate credits, and you should use both: - `Idempotency-Key` protects against retries. Using the same key within 24 hours returns the stored response instead of creating another credit. - `client_transaction_id` identifies the actual credit. It must be unique for the account and doesn't expire. Reusing the same ID with the same amount returns `200` with `Idempotent-Replay: true` and doesn't move any money. Reusing it with a different amount returns `409 adjustment_already_applied`. Send both on every funding request. Parameters: account_id (in: path, string(^acct_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: amount (object) REQUIRED — An amount in **integer minor units** with an ISO 4217 currency. Never a amount.amount (integer) REQUIRED — Minor units. `1050` with currency `SGD` is S$10.50. amount.currency (string(^[A-Z]{3}$)) REQUIRED — Uppercase ISO 4217 alphabetic code. type (enum(purchase | withdrawal | transfer | load | fee | refund | adjustment)) — What this movement was for. It becomes the `type` of the description (string) reference (string) — Your label for the movement, for reconciliation. Free text with no client_transaction_id (string) — Your identifier for this movement. Unique per account, across Responses: 200 The updated account. Carries `Idempotent-Replay: true` when the 409 `adjustment_already_applied`: this `client_transaction_id` was already 422 Well-formed but fails a business rule. #### POST /v1/accounts/{account_id}/debit operationId: debitAccount summary: Debit an account Removes money from an account. If the `available_balance` is too low, the request fails with `insufficient_funds` and a `402`. Money held by pending authorizations can't be debited. There are two ways to prevent duplicate debits, and you should use both: - `Idempotency-Key` protects against retries. Using the same key within 24 hours returns the stored response instead of creating another debit. - `client_transaction_id` identifies the actual debit. It must be unique for the account and doesn't expire. Reusing the same ID with the same amount returns `200` with `Idempotent-Replay: true` and doesn't move any money. Reusing it with a different amount returns `409 adjustment_already_applied`. Send both on every debit request. Parameters: account_id (in: path, string(^acct_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: amount (object) REQUIRED — An amount in **integer minor units** with an ISO 4217 currency. Never a amount.amount (integer) REQUIRED — Minor units. `1050` with currency `SGD` is S$10.50. amount.currency (string(^[A-Z]{3}$)) REQUIRED — Uppercase ISO 4217 alphabetic code. type (enum(purchase | withdrawal | transfer | load | fee | refund | adjustment)) — What this movement was for. It becomes the `type` of the description (string) reference (string) — Your label for the movement, for reconciliation. Free text with no client_transaction_id (string) — Your identifier for this movement. Unique per account, across Responses: 200 The updated account. Carries `Idempotent-Replay: true` when the 402 The account's available balance is too low. 409 `adjustment_already_applied`: this `client_transaction_id` was already 422 Well-formed but fails a business rule. #### GET /v1/accounts/{account_id}/cards operationId: listAccountCards summary: List an account's cards Returns every card that spends from this account. An account can fund several cards; a card spends from exactly one account. Parameters: account_id (in: path, string(^acct_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED limit (in: query, integer) — Page size. Values above the maximum are rejected, not clamped. starting_after (in: query, string) — Opaque cursor for the next page. Valid for 24 hours. Responses: 200 A page of cards. 401 Credentials are missing, invalid, or expired. Do not retry. 404 The resource does not exist, or belongs to another organization. The two #### POST /v1/accounts/{account_id}/block operationId: blockAccount summary: Block an account Suspends the account. Every card funded by it declines with `account_blocked` until the account is unblocked, and credits and debits are refused. Blocking is reversible and does not touch the balances. Holds already placed remain in force and still release on clearing or expiry. Parameters: account_id (in: path, string(^acct_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: reason (string) — Free text recorded on the audit entry. Never shown to the Responses: 200 The blocked account. 404 The resource does not exist, or belongs to another organization. The two 422 Well-formed but fails a business rule. #### POST /v1/accounts/{account_id}/unblock operationId: unblockAccount summary: Unblock an account Returns a blocked account to `active`. Cards funded by it resume authorizing immediately. Parameters: account_id (in: path, string(^acct_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Responses: 200 The unblocked account. 404 The resource does not exist, or belongs to another organization. The two 422 Well-formed but fails a business rule. ### Cards The payment instrument. A virtual card, spending from exactly one account. #### GET /v1/cards operationId: listCards summary: List cards Returns a page of cards. Filter by `status` to find, for example, every frozen card, or by `program_id` to narrow to one product. Parameters: limit (in: query, integer) — Page size. Values above the maximum are rejected, not clamped. starting_after (in: query, string) — Opaque cursor for the next page. Valid for 24 hours. status (in: query, enum(pending | inactive | active | frozen | expired | closed)) — Return only cards in this status. form_factor (in: query, enum(virtual | physical)) — Return only cards of this form factor. account_id (in: query, string(^acct_[0-9A-HJKMNP-TV-Z]{26}$)) — Return only cards that spend from this account. program_id (in: query, string(^prog_[0-9A-HJKMNP-TV-Z]{26}$)) — Return only cards belonging to this program. Omit it and the page Responses: 200 A page of cards. 400 The request is malformed or contains an unrecognized parameter. 401 Credentials are missing, invalid, or expired. Do not retry. 429 Rate limit exceeded. Honor `Retry-After`. #### POST /v1/cards operationId: createCard summary: Issue a card Issues a virtual or a physical card for an account. A virtual card needs no more than the cardholder and the account. A physical card also needs `embossed_name`, the name printed on it, and a `delivery_address`. To have plastic made for a card you already hold, use `POST /v1/cards/{card_id}/request-physical` instead. The response only includes `last_four`. The full card number and CVV are never returned here. Getting them is a separate, separately scoped and audited operation. A card is never usable straight away. It goes through the same statuses on every program: 1. It starts as `pending` while the processor issues it. On a program that issues at once, the response already says `inactive`. 2. It becomes `inactive` when the processor has the card, and you get `card.issued`. 3. It becomes `active` when you call `POST /v1/cards/{card_id}/activate`, and you get `card.activated`. Until then every authorization is declined. Parameters: Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: cardholder_id (string(^chd_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED account_id (string(^acct_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED — The account the card spends from. The card takes this account's form_factor (enum(virtual | physical)) REQUIRED embossed_name (string) — The name printed on the card. Required for a physical card. delivery_address (object) — Where a physical card is posted, and required for one. A virtual card reference (string) Responses: 201 Card accepted. `pending` while the processor issues it, or 400 The request is malformed or contains an unrecognized parameter. 422 Well-formed but fails a business rule. #### GET /v1/cards/{card_id} operationId: getCard summary: Retrieve a card Returns a single card. The full card number is never included — `last_four` is the only part of the number returned here. Parameters: card_id (in: path, string(^card_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Responses: 200 The card. 404 The resource does not exist, or belongs to another organization. The two #### POST /v1/cards/{card_id}/freeze operationId: freezeCard summary: Freeze a card Temporarily blocks the card. Reversible — use this rather than closing for a card the cardholder has merely mislaid. Parameters: card_id (in: path, string(^card_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: reason (string) — Free text passed to the processor with the status change. Never Responses: 200 The frozen card. 422 Well-formed but fails a business rule. #### POST /v1/cards/{card_id}/unfreeze operationId: unfreezeCard summary: Unfreeze a card Returns a frozen card to `active`. Note that the card returns to `active` regardless of what its state was before it was frozen — lifecycle and freeze state share one field. See the card lifecycle guide. Parameters: card_id (in: path, string(^card_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Responses: 200 The unfrozen card. 422 Well-formed but fails a business rule. #### POST /v1/cards/{card_id}/replace operationId: replaceCard summary: Replace a card Issues a new card against the same cardholder and account, then closes this one. Returns the new card. The new card has a new `id`, number and expiry, and keeps the original's `reference`. The new card follows the usual issuing flow: `active` straight away, or `pending` and then `active` with `card.issued` on a program that issues asynchronously. The original is closed and emits `card.closed`. The new card is issued before the original is closed, so a failure never leaves the cardholder without a card. If the close fails, the response is an error naming both cards and the original stays `active`: close it with `POST /v1/cards/{card_id}/close` and do not repeat the replacement, which would issue another card. A closed card cannot be replaced. Parameters: card_id (in: path, string(^card_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: reason (enum(lost | stolen | damaged | expired | compromised | other)) REQUIRED Responses: 201 The replacement card. 422 Well-formed but fails a business rule. #### POST /v1/cards/{card_id}/close operationId: closeCard summary: Close a card Permanently terminates the card. **Not reversible.** Any network tokens derived from it are invalidated. To block temporarily, freeze instead. Parameters: card_id (in: path, string(^card_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: reason (string) — Free text passed to the processor with the status change. Never Responses: 200 The closed card. 422 Well-formed but fails a business rule. #### POST /v1/cards/{card_id}/activate operationId: activateCard summary: Activate a card Makes an `inactive` card spendable. It is a server-to-server call: there is no hosted page and no URL to open, and the response is the card. The same call activates a virtual card and a physical one. Call it when you receive `card.issued` and you are ready for the card to be used. - A `pending` card is refused with `card_invalid_state`. Wait for `card.issued`; the card is never issued by calling this endpoint. - A card that is already `active` is refused with `card_already_activated`. - The cardholder must still be verified. A cardholder who is no longer `approved` is refused with `kyc_not_approved`. Activation takes effect at once. A card that fails to activate stays `inactive` and can be activated again. Requires the `cards:write` scope. Parameters: card_id (in: path, string(^card_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Responses: 200 The card, now `active`. 403 Credentials are valid but not permitted. Do not retry. 404 The resource does not exist, or belongs to another organization. The two 422 Well-formed but fails a business rule. #### POST /v1/cards/{card_id}/request-physical operationId: requestPhysicalCard summary: Request a physical card Orders plastic for a card issued without it. The response is the same card, not a new one: same `id`, same number, same balance, with `form_factor` now `physical` and `fulfillment.status` `pending`. It keeps working while the plastic is made. There is no request body. The name printed on the card and the address it is posted to were both fixed when the card was issued, and this operation cannot revise either. - A card that is not `active` is refused with `card_invalid_state`. So is a card that is already `physical`, which is what a second request gets. - A closed card is refused with `card_closed`. - A program that does not issue physical cards is refused with `unsupported_form_factor`. No event is emitted. Nothing reports when a card is posted, so the next thing you learn is the cardholder telling you it arrived — call `POST /v1/cards/{card_id}/activate` then to enable the card for use in person. You can also issue a physical card directly, without a virtual card first, with `POST /v1/cards` and `form_factor: physical`. Requires the `cards:write` scope. Parameters: card_id (in: path, string(^card_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Responses: 200 The card, now `physical`, with the plastic on its way. 403 Credentials are valid but not permitted. Do not retry. 404 The resource does not exist, or belongs to another organization. The two 422 Well-formed but fails a business rule. #### POST /v1/cards/{card_id}/pin-key operationId: createCardPinKey summary: Get a PIN encryption key Returns a short-lived RSA public key to encrypt a PIN to. It is the first of two server-to-server calls that set a PIN: you encrypt the PIN to this key, then send it with `POST /v1/cards/{card_id}/pin`. The key is for one card and expires after a few minutes. Ask for it when the cardholder is about to enter their PIN, not when the screen loads. It opens one PIN: once it has been sent with a PIN, whatever the outcome, it can't be used again. Every call returns a new key. `Idempotency-Key` is accepted like on every other `POST`, but it is not used here, because a replay would hand back a key that has already been spent. Only a physical card that is `active` has a PIN to set. Any other card is refused with `card_invalid_state`, and a closed card with `card_closed`. Requires the `cards:secrets` scope. A program whose processor does not take a PIN by a call does not have this endpoint at all. Parameters: card_id (in: path, string(^card_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Responses: 201 The key to encrypt the PIN to. 403 Credentials are valid but not permitted. Do not retry. 404 The resource does not exist, or belongs to another organization. The two 422 Well-formed but fails a business rule. #### POST /v1/cards/{card_id}/pin operationId: setCardPin summary: Set a PIN Sets the PIN of a physical card from a PIN you have encrypted. It is a server-to-server call: there is no page to open. The same call sets a first PIN and resets a forgotten one. 1. Get a key with `POST /v1/cards/{card_id}/pin-key`. 2. Encrypt the PIN, as its digits, to that key with RSA-OAEP using SHA-256, and encode the result as base64. 3. Send `key_id` and the encrypted PIN here. **Encrypt on the cardholder's device.** The PIN is ciphertext while it passes through your systems, and it stays out of PCI DSS scope only while your servers never see it in the clear. A server that handles the PIN unencrypted is in scope. We check only what a PIN needs to be usable: digits, in the length the key response gives as `pin_length`. Anything else is your policy, such as whether to refuse repeated digits or sequences, and is yours to validate before you encrypt. A PIN that isn't digits of that length is refused with `pin_invalid`. A key that is unknown, already used or expired is refused with `invalid_parameter`; get a new one. Only a physical card that is `active` has a PIN to set. Any other card is refused with `card_invalid_state`, and a closed card with `card_closed`. Never write the PIN, encrypted or not, to a log, an analytics event, web storage or a support ticket. Requires the `cards:secrets` scope. Parameters: card_id (in: path, string(^card_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: key_id (string) REQUIRED — The `key_id` of the key the PIN is encrypted to. encrypted_pin (string) REQUIRED — The PIN as digits, encrypted with RSA-OAEP using SHA-256 to the key's Responses: 204 The PIN is set. Nothing is returned. 400 The request is malformed or contains an unrecognized parameter. 403 Credentials are valid but not permitted. Do not retry. 404 The resource does not exist, or belongs to another organization. The two 422 Well-formed but fails a business rule. #### POST /v1/cards/{card_id}/secrets operationId: revealCardSecrets summary: Reveal card secrets Returns the card's number and security code, each encrypted to a public key you supply. It is a server-to-server call: there is no page to open, and only the holder of the matching private key can read the values. Send an RSA public key in PEM form, of at least 2048 bits. The response carries `encrypted_pan` and `encrypted_cvv`, each encrypted with RSA-OAEP using SHA-256 and encoded as base64. `last_four` and `expires_at` come back in the clear. Only an `active` card has details to show. Any other card is refused with `card_invalid_state`, and a closed card with `card_closed`. The response is never cached. `Idempotency-Key` is accepted like on every other `POST`, but it is not used here: a replay would have to keep a copy of the first response, so every call is answered afresh with a new ciphertext. Never write the decrypted values to a log, an analytics event, web storage or a support ticket. Requires the `cards:secrets` scope. A program whose processor cannot return card details does not have this endpoint at all. Parameters: card_id (in: path, string(^card_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: public_key (string) REQUIRED — The RSA public key the card's secrets are encrypted to, in PEM form: a Responses: 200 The card's number and security code, encrypted to your key. 400 The request is malformed or contains an unrecognized parameter. 403 Credentials are valid but not permitted. Do not retry. 404 The resource does not exist, or belongs to another organization. The two 422 Well-formed but fails a business rule. ### 3-D Secure Cardholder authentication on an online purchase, before the authorization. Almost all of it belongs to the Access Control Server: it decides whether to challenge, picks the method, and verifies the answer. One case reaches this API. An out-of-band challenge is answered in an application of yours rather than on the merchant's page, so the authentication becomes an object you read and answer while the merchant's checkout waits. #### GET /v1/authentications operationId: listAuthentications summary: List authentications Returns a page of out-of-band authentications, newest first. This is a recovery path for a broken push channel, not a polling loop. A challenge expires in minutes, so polling often enough to catch one costs more rate limit than it is worth; subscribe to `authentication.challenged` and read this when your own notifications fail. Requires the `authentications:read` scope. Parameters: limit (in: query, integer) — Page size. Values above the maximum are rejected, not clamped. starting_after (in: query, string) — Opaque cursor for the next page. Valid for 24 hours. card_id (in: query, string(^card_[0-9A-HJKMNP-TV-Z]{26}$)) status (in: query, enum(pending | approved | declined | timed_out)) Responses: 200 A page of authentications. 400 The request is malformed or contains an unrecognized parameter. 401 Credentials are missing, invalid, or expired. Do not retry. 429 Rate limit exceeded. Honor `Retry-After`. #### GET /v1/authentications/{authentication_id} operationId: getAuthentication summary: Retrieve an authentication Returns a single authentication. `authentication.challenged` already carries the whole object, so a handler that trusts the event does not need this call. Requires the `authentications:read` scope. Parameters: authentication_id (in: path, string(^authn_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Responses: 200 The authentication. 403 Credentials are valid but not permitted. Do not retry. 404 The resource does not exist, or belongs to another organization. The two #### POST /v1/authentications/{authentication_id}/approve operationId: approveAuthentication summary: Approve an authentication Tells the Access Control Server the cardholder proved who they are, and releases the merchant's checkout to continue. Call it only after a step-up on the device, immediately before: a biometric or the device passcode. An application that approves because it happens to be unlocked has removed the authentication rather than moved it. Answer once. A second answer under a different `Idempotency-Key` returns `422 authentication_not_pending`. Requires the `authentications:write` scope. Parameters: authentication_id (in: path, string(^authn_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Responses: 200 The authentication, now `approved`. 403 Credentials are valid but not permitted. Do not retry. 404 The resource does not exist, or belongs to another organization. The two 422 Well-formed but fails a business rule. #### POST /v1/authentications/{authentication_id}/decline operationId: declineAuthentication summary: Decline an authentication Tells the Access Control Server the cardholder refused, or that your step-up failed. The purchase fails at the merchant. Declining is worth doing rather than letting the challenge expire: the cardholder who taps "this wasn't me" gets an immediate answer, and you get a record of a refusal instead of a timeout. Requires the `authentications:write` scope. Parameters: authentication_id (in: path, string(^authn_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Responses: 200 The authentication, now `declined`. 403 Credentials are valid but not permitted. Do not retry. 404 The resource does not exist, or belongs to another organization. The two 422 Well-formed but fails a business rule. ### Transactions Settled money movement. #### GET /v1/transactions operationId: listTransactions summary: List transactions Returns a page of transactions, most recently settled first. Transactions are read-only. Parameters: limit (in: query, integer) — Page size. Values above the maximum are rejected, not clamped. starting_after (in: query, string) — Opaque cursor for the next page. Valid for 24 hours. card_id (in: query, string(^card_[0-9A-HJKMNP-TV-Z]{26}$)) account_id (in: query, string(^acct_[0-9A-HJKMNP-TV-Z]{26}$)) Responses: 200 A page of transactions. 400 The request is malformed or contains an unrecognized parameter. 401 Credentials are missing, invalid, or expired. Do not retry. 429 Rate limit exceeded. Honor `Retry-After`. #### GET /v1/transactions/{transaction_id} operationId: getTransaction summary: Retrieve a transaction Returns a single transaction. Parameters: transaction_id (in: path, string(^txn_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Responses: 200 The transaction. 404 The resource does not exist, or belongs to another organization. The two ### eSIM Packages The catalog of mobile data plans. A package is a purchasable plan: a data volume, a validity period, and the countries it works in. Read-only and shared across the platform. It changes when coverage or pricing changes, so cache it for minutes rather than days — and retrieve the one package you are about to buy immediately beforehand, because ordering a frozen or withdrawn package is refused. #### GET /v1/esim/packages operationId: listEsimPackages summary: List eSIM packages Returns the packages on sale — where each one works, how long it lasts, and what it costs you. **Only `status: available` is listed.** A package we have frozen or withdrawn is not here, so a storefront can render this response wholesale without checking each row. To read one anyway — an order from last month references it — retrieve it by id. **This list is not paginated, and it is not sortable.** The catalog is small and bounded, so the response carries all of it. There is no `limit`, no cursor, and no `has_more` to loop on. Narrow it with `country`; sort it yourself — you already hold every row, so a server round trip to reorder them would buy nothing. Packages come back ordered by `name`, ascending. The order is stable, so two calls with the same filter return the same sequence. Nothing is provisioned here. A package becomes an eSIM profile only when it is ordered. `volume` is in megabytes, and it carries its own `type`. Read both: the same `1024` is 1 GB on a `total` plan and 30 GB on a 30-day `daily` one. The catalog is shared and changes rarely. Cache it and refresh on a timer rather than calling this on every page view. Parameters: country (in: query, string(^[A-Z]{2}$)) — Return only packages valid in this country, whether they are Responses: 200 The catalog, in full. 400 The request is malformed or contains an unrecognized parameter. 401 Credentials are missing, invalid, or expired. Do not retry. 429 Rate limit exceeded. Honor `Retry-After`. #### GET /v1/esim/packages/{package_id} operationId: getEsimPackage summary: Retrieve an eSIM package Returns a single package, **whatever its status**. Unlike the list, this resolves a package we have frozen or withdrawn, so an order placed months ago still names something readable. Read a package here immediately before you charge for it. A cached list can be stale on the two fields that matter at that moment — `price` and `status` — and neither changes in a way your copy can detect on its own. Parameters: package_id (in: path, string(^epkg_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Responses: 200 The package. 401 Credentials are missing, invalid, or expired. Do not retry. 404 The resource does not exist, or belongs to another organization. The two #### GET /v1/esim/countries operationId: listEsimCountries summary: List supported countries Returns every country the eSIM catalog covers. This is the vocabulary the `country` filter on [list packages](https://developer.aletaplanet.com/api-reference/resources/list-esim-packages) accepts. A code that is not here matches nothing, which is why it is worth reading once at integration time rather than hardcoding a list of your own: coverage moves as packages are added and withdrawn, and this list moves with it. Every country here has at least one `available` package. A country whose only packages are frozen or withdrawn drops out. Not paginated — there are fewer than two hundred of these. Ordered by `name`, ascending, and stably. Responses: 200 The covered countries. 401 Credentials are missing, invalid, or expired. Do not retry. 429 Rate limit exceeded. Honor `Retry-After`. ### eSIM Orders Buying a package. An order allocates an eSIM from inventory and provisions the package onto it before the call returns. There is no queue and no callback: a `201` means the eSIM is ready, and almost every failure means nothing was bought. The exception is a timeout against the network that provisions the profile. Then the outcome is genuinely unknown to us for a short while, and the order appears in your list without an `esim_id` until we settle it. That is the one case where an error does not mean "nothing happened" — and it is why the list carries unsettled orders at all. #### GET /v1/esim/orders operationId: listEsimOrders summary: List eSIM orders Returns a page of your orders, newest first. Filter by `reference` to answer the question this endpoint exists for: **did my order go through?** A request that timed out has an unknown outcome, and your own order id is the only handle you are guaranteed to still hold. `reference` is unique within your organization, so this returns one row or none. It answers in three ways, and the third is why the row exists: - **no row** — we never took the order; nothing was charged. - **a settled row** — it went through. A package order carries an `esim_id`; a top-up carries a `package_id` and a `price`. - **an unsettled row** — we do not know yet. The provisioning network timed out and we are settling it, usually within minutes. Ask again rather than re-ordering; a new `reference` would be a second purchase. **Branch on `object` before you check**, because there is no single field that answers it for both. The two order kinds learn things in opposite orders, so each is still missing what the other already knows — see `EsimOrderSummary` and `EsimTopUpOrder` for which is which. `Idempotency-Key` covers the same ground for 24 hours and returns the original response outright, so reach for that first if you still have the key. This is what is left when you do not. Package purchases and top-ups share this ledger. **Branch on `object`** — `esim_order` against `esim_top_up_order` — rather than assuming every row has the same fields. No row carries an `activation_code`, for the reason a list of eSIMs does not carry one either. Retrieve the order, or the eSIM, when you need to deliver install instructions. Parameters: limit (in: query, integer) — Page size. Values above the maximum are rejected, not clamped. starting_after (in: query, string) — Opaque cursor for the next page. Valid for 24 hours. reference (in: query, string) — Return only the order carrying this reference. Responses: 200 A page of orders. 400 The request is malformed or contains an unrecognized parameter. 401 Credentials are missing, invalid, or expired. Do not retry. 429 Rate limit exceeded. Honor `Retry-After`. #### POST /v1/esim/orders operationId: createEsimOrder summary: Order an eSIM package Buys one package. `package_id` names what you are buying; the package must be `available` at the moment of the call, which is why it is worth [retrieving it](https://developer.aletaplanet.com/api-reference/resources/get-esim-package) immediately beforehand rather than trusting a cached list. Ordering a frozen or withdrawn package is refused with `esim_package_unavailable`. An eSIM is allocated from our inventory and the package provisioned onto it before this call returns. There is no queue and no callback: a `201` means the eSIM is ready, and almost every failure means nothing was bought. **The exception is a `502` from a timeout** against the network that provisions the profile. We cannot tell in that moment whether it took the order, so we neither charge you nor claim it failed. The order appears in your order list without an `esim_id` until we settle it. Look it up by your own `reference`. Do not re-send under a new `reference` — that is a second purchase. If you still hold the `Idempotency-Key`, replaying it is the better move. `reference` is your order id, and it must be unique within your organization. `activation_code` on the response is the LPA string a traveller scans. Encode it as a QR yourself — it is a short string, every platform has a one-line library for it, and a hosted image would be a URL that installs somebody's eSIM for whoever opens it. `price` on the response is the price charged, captured at order time. It does not track the catalog afterwards. **Send an `Idempotency-Key`.** A timed-out order retried without one is a second purchase, not a second look at the first. Parameters: Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: package_id (string(^epkg_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED — The package to buy. Must be `available`. reference (string) REQUIRED — Your order id for this purchase. Required, and **unique within Responses: 201 The order. 400 The request is malformed or contains an unrecognized parameter. 404 The resource does not exist, or belongs to another organization. The two 409 Idempotency key reused with a different body, or a concurrent 422 Well-formed but fails a business rule. #### GET /v1/esim/orders/{order_id} operationId: getEsimOrder summary: Retrieve an eSIM order Returns one order. **Branch on `object`:** a package purchase comes back as `esim_order` and carries the `activation_code` the create call returned, while a top-up comes back as `esim_top_up_order` and carries none — it added data to a profile that was already installed. Re-reading a package order is how you recover install instructions without keeping the secret yourself. If you are here because a create request timed out and you no longer have its `Idempotency-Key`, you will not have this id either — look the order up by your own `reference` instead. An order found that way may still be settling, in which case it comes back without an `esim_id` or an `activation_code` (without a `top_up_id`, for a top-up). That is not an error and not a `404`; it means the answer is not in yet. Parameters: order_id (in: path, string(^eord_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Responses: 200 The order. 401 Credentials are missing, invalid, or expired. Do not retry. 404 The resource does not exist, or belongs to another organization. The two ### eSIM Profiles The provisioned profile a traveller installs, and the packages running on it. One eSIM accumulates packages as it is topped up. The activation code is the profile. It is returned when you retrieve one eSIM and deliberately left off a listing, so a page cannot become a hundred secrets in one response. #### GET /v1/esims operationId: listEsims summary: List allocated eSIMs Returns a page of the eSIMs allocated to you, newest first. Rows are `EsimSummary`, which leaves out two fields on purpose. `packages` is one: an eSIM accumulates them as it is topped up, and inlining every one on every row turns a page into a payload nobody asked for. `activation_code` is the other, and for a different reason. Each one installs a profile, so a page of a hundred would be a hundred secrets in a single response — easy to log, easy to proxy into a browser, impossible to take back. Retrieve an eSIM when you need to deliver its install instructions. Filter by `order_reference` to find the eSIM behind one of your orders, or by `iccid` when a traveller reads theirs off the handset. Parameters: limit (in: query, integer) — Page size. Values above the maximum are rejected, not clamped. starting_after (in: query, string) — Opaque cursor for the next page. Valid for 24 hours. order_reference (in: query, string) — Return only the eSIM allocated to the order carrying this iccid (in: query, string(^[0-9]{19,20}$)) — Return only the eSIM with this ICCID. Responses: 200 A page of eSIMs. 400 The request is malformed or contains an unrecognized parameter. 401 Credentials are missing, invalid, or expired. Do not retry. 429 Rate limit exceeded. Honor `Retry-After`. #### GET /v1/esims/{esim_id} operationId: getEsim summary: Retrieve an eSIM Returns one eSIM and every package on it. `packages` is the whole history, not just what is live — a package that has expired stays listed. Read `expires_at` to tell them apart rather than assuming the array holds only current ones. Data counters come from the network and settle with a delay. Treat `volume.remaining` as recent rather than exact, and never as a billing figure. Parameters: esim_id (in: path, string(^esim_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Responses: 200 The eSIM. 401 Credentials are missing, invalid, or expired. Do not retry. 404 The resource does not exist, or belongs to another organization. The two ### eSIM Top-ups More data on a package already running. Nothing is installed and nothing is replaced: the traveller keeps the profile they have, so there is no new activation code. Which top-ups an eSIM can take depends on what is running on it, so read the list immediately beforehand rather than trusting a cached one. #### GET /v1/esims/{esim_id}/top_ups operationId: listEsimTopUps summary: List available top-ups Returns the add-ons this eSIM can take right now, **grouped by the package each one extends**. An eSIM can carry several packages at once, and an add-on belongs to exactly one of them — a top-up extends the package it was sold against rather than the profile as a whole. The grouping is what tells you which is which, so read `package_id` before you sell. Only running packages appear. A package that has been bought but never reached a network cannot be extended yet, and a package sold without add-ons is left out rather than listed empty — so an empty `data` means there is nothing to sell on this eSIM today. Within a group, add-ons come smallest first. Not paginated: a package carries a handful of these, not a catalog. Parameters: esim_id (in: path, string(^esim_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Responses: 200 The add-ons available on this eSIM. 401 Credentials are missing, invalid, or expired. Do not retry. 404 The resource does not exist, or belongs to another organization. The two #### POST /v1/esims/{esim_id}/top_ups operationId: createEsimTopUp summary: Order a top-up Adds data to a package already running on this eSIM. Nothing is installed and nothing is replaced: the traveller keeps the profile they have, and the data lands against the package the add-on belongs to. There is no new activation code because there is no new profile. `top_up_id` must be one currently listed for this eSIM. An add-on that has stopped being sold, or one belonging to a package that is not running, is refused with `esim_top_up_unavailable` — read the list immediately beforehand rather than trusting a cached one. One top-up, bought once. A top-up lands on the allowance the package is running **today** — it cannot be bought ahead for a later day, and it expires with the package it extends, so one bought late in that package's life buys less time than the same one bought early. **Send an `Idempotency-Key`.** A timed-out top-up retried without one is a second purchase. Parameters: esim_id (in: path, string(^esim_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: top_up_id (string(^etop_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED — The add-on to order, from this eSIM's own top-up list. reference (string) REQUIRED — Your order id for this purchase. Required, and unique within your Responses: 201 The top-up order. 400 The request is malformed or contains an unrecognized parameter. 404 The resource does not exist, or belongs to another organization. The two 409 Idempotency key reused with a different body, or a concurrent 422 Well-formed but fails a business rule. ### eSIM H5 A link to a hosted mobile page for one cardholder. You create the link from your backend and send the cardholder to it; the page itself is ours. A link lasts 15 minutes and can be opened any number of times until then. Create one when the cardholder asks for the page, not ahead of time. #### POST /v1/esim/h5_links operationId: createEsimH5Link summary: Create an H5 link Creates a link to the hosted eSIM page for one cardholder. Requires the `esim:write` scope. `cardholder_id` names who the page is for, and must be a cardholder in your organization. One outside it is refused with `404`, exactly as if it did not exist. **The link lasts 15 minutes.** `expires_at` is always `created_at` plus 15 minutes, and the lifetime is not configurable. Until then the link opens as many times as you like; after it, the page refuses it and you create a new one. A link cannot be retrieved, listed, or revoked once created. Create it from your backend when the cardholder asks for the page, and redirect them straight to `url`. A link sent by email, SMS, or push has usually expired by the time it is opened — send a link to your own endpoint instead, and have that endpoint create a fresh H5 link when it is followed. **Treat `url` as a credential.** Anyone holding it acts as that cardholder until it expires. Keep it out of logs, analytics, and storage. A retry with the same `Idempotency-Key` returns the original link, `expires_at` included — even after it has expired, since keys are kept for 24 hours. Use a fresh key for every new link. Parameters: Idempotency-Key (in: header, string) — A client-generated unique key making the request safe to retry. Generate Request body: cardholder_id (string(^chd_[0-9A-HJKMNP-TV-Z]{26}$)) REQUIRED — The cardholder the page is for. Must be in your organization. Responses: 201 The link. 400 The request is malformed or contains an unrecognized parameter. 401 Credentials are missing, invalid, or expired. Do not retry. 403 Credentials are valid but not permitted. Do not retry. 404 The resource does not exist, or belongs to another organization. The two 422 Well-formed but fails a business rule. 429 Rate limit exceeded. Honor `Retry-After`. ## Enumerations ### KycStatus Identity verification state. Only `approved` permits card issuance. - not_started - pending - approved - rejected - review_required ### CardholderType The kind of cardholder. Only `individual` is supported for now. - individual ### CardholderCreateType The kind of cardholder to create. Only `individual` is supported for now. - individual ### IdentityDocumentType The kind of identity document a cardholder was identified by. - passport - drivers_license - national_id - umid - postal_id - voters_id - sss - tin - other ### KycMethod How the cardholder's identity was established. - document_and_selfie - document_only - database_check - in_person - other ### Gender As recorded by the verification. `unspecified` is a valid answer and not a missing one. - male - female - unspecified ### KycDocumentKind What an image shows. One of each at most per attestation. `identity` is the document itself — passport, national ID, licence. `residence` is a proof of address. `selfie` is the liveness capture the document is matched against. - identity - residence - selfie ### AccountStatus Account state. `blocked` is reversible and suspends all activity on the account and every card it funds. There is deliberately no `closed` state. Account closure is not currently exposed, and documenting a state nothing can produce is worse than documenting a smaller surface — it can be added later without breaking clients. - active - blocked ### CardFormFactor - virtual - physical ### CardStatus Card lifecycle state. Virtual and physical cards share one set of statuses and one path: `pending` while the processor issues the card, `inactive` once it has the card (`card.issued`), and `active` when you call `POST /v1/cards/{card_id}/activate` (`card.activated`). A card that is not `active` declines every authorization. Note that `frozen` sits in the same field as the lifecycle states, so unfreezing always returns a card to `active` rather than to whatever it was before. See the card lifecycle guide. - pending - inactive - active - frozen - expired - closed ### CardFulfillmentStatus How far the plastic has got. Separate from `status`, which is about whether the card can be used: a `physical` card can be `active` and spending online while its plastic is still being made. `not_applicable` is a card with no plastic to make. `pending` is plastic ordered. `delivered` is the cardholder confirming it arrived, which is also when the card is enabled for use in person. Not every status is reachable on every program. Manufacturing and delivery happen outside the API, and a processor that reports nothing between `pending` and `delivered` never produces `personalized`, `shipped` or `returned`. See [fulfillment and shipping tracking](https://developer.aletaplanet.com/docs/products/card-issuing/cards/fulfillment-and-shipping-tracking). - not_applicable - pending - personalized - shipped - delivered - returned ### AuthenticationStatus | Status | Meaning | | --- | --- | | `pending` | Waiting for an answer. The merchant's checkout is open | | `approved` | Answered yes. The authorization follows | | `declined` | Answered no, by you or by the cardholder | | `timed_out` | Nobody answered before `expires_at`. The purchase failed at the merchant | - pending - approved - declined - timed_out ### AuthenticationMethod How the cardholder was asked. Only out-of-band challenges become objects here; a passcode challenge is run end to end by the Access Control Server and is never visible through this API. - out_of_band ### TransactionType What the transaction was for. It does **not** say which way the money moved — read `direction` for that, because every type occurs in both directions once reversals are counted. | Value | Meaning | | --- | --- | | `purchase` | A card purchase at a merchant | | `withdrawal` | Cash taken at an ATM or over a counter | | `transfer` | Value moved between accounts | | `load` | Value added to the account outside a card transaction — funding | | `fee` | A charge levied on the account, including taxes | | `refund` | A merchant returned value against an earlier purchase | | `adjustment` | A correction, and the value used when nothing more specific applies | Treat this as an open enum. New values are added as products grow, and a client that switches on it should have a default branch rather than fail on one it does not recognise. - purchase - withdrawal - transfer - load - fee - refund - adjustment ### TransactionDirection Which way the value moved, from the account's point of view. `credit` increases `ledger_balance`, `debit` decreases it. Carried explicitly rather than inferred from `type`, because the inference does not hold: a purchase and its cancellation share a type and move opposite ways. `amount` is always positive and this field says which way it points. - credit - debit ### EsimStatus `active` — usable. `frozen` — suspended, and reversible. Packages on it keep their remaining validity while it is frozen. `closed` — deregistered for good. A closed eSIM cannot be revived and cannot take another package. - active - frozen - closed ### EsimPackageStatus Whether the package is on sale. `available` — orderable now. `frozen` — we have suspended it, and the suspension is reversible. It is not orderable while frozen, but everything already sold against it keeps working. `disabled` — withdrawn for good. Kept readable so an order that references it still resolves, never orderable again. - available - frozen - disabled