Download OpenAPI specification:
The Relay API lets Pocket Customers manage Accounts and recipients, move money, submit Batches of transfers, and reconcile Account-relative balances and activity.
cus_…) is the person or business represented by the API key.acc_…) holds a Customer's funds and has its own ID.
A Customer can own multiple Accounts in the same currency.Pocket publishes the Relay API as an OpenAPI specification rather than client SDKs.
| Resource | Purpose |
|---|---|
| Accounts | Create, list, retrieve and label the Customer's funds containers. |
| Balances | Read current funds by Account. |
| Recipients | Manage a business Customer's payout roster. |
| Transactions | Read inbound and outbound activity together. |
| Transfers | Create and reconcile same-Customer Account transfers and Customer-to-Customer P2P. |
| Deposits | Reconcile money received by the Customer. |
| Batch transfers | Submit and reconcile asynchronous Customer-to-Customer payouts. |
Specification 0.6.0 uses the existing /v1 paths. This is the Sandbox
release candidate; it does not establish what is deployed to production.
| Environment | API base URL | Documentation |
|---|---|---|
| Sandbox | https://api.relay.sandbox.pocketmoney.global |
Sandbox docs |
| Production | https://api.relay.pocketmoney.global |
Production docs |
Use a key issued in the environment you are calling. Do not use a production key or production funds for Sandbox tests. Paths and JSON examples below are relative to the selected API base URL. Replace example IDs with resources in that environment. Use a new idempotency key for each new operation, and reuse that key when retrying the same operation.
For example, read Sandbox Accounts with an environment-specific bearer key:
curl 'https://api.relay.sandbox.pocketmoney.global/v1/accounts' \
-H "Authorization: Bearer $RELAY_SANDBOX_API_KEY"
The release contains 23 public operations across the seven resource families above. New Accounts, internal Transfers and Batches use USD. An Account's owner, currency, default designation and lifecycle status cannot be changed through these endpoints. Account updates change the label, reference and metadata.
Operations require appropriate permissions and eligible Customers and Accounts. Payments also require sufficient funds. Customer-to-Customer payments may have a limit on the total sent in the previous 30 days. This includes pending and settled payments. The limit can vary by Customer. Batch acceptance confirms submission; check each item's status to confirm payment.
Send the API key as a bearer token:
Authorization: Bearer eak_v1_...
Every request runs as the Customer attached to that key. A source account_id
must belong to it. Direct retrieval of another Customer's resource returns
404 as if it did not exist. List-filter error behaviour is described on each
operation. Recipient, Account-create and Batch operations require an
active, unblocked business Customer. Pending, suspended, closed, blocked or
non-business Customers cannot use those business-only operations.
The key must also contain every scope named by the operation. The V1 scopes are
accounts:read, accounts:write, balances:read, recipients:read, recipients:write, transactions:read,
transfers:read, transfers:write, deposits:read, batches:read and
batches:write. Broad read grants every read scope; broad write grants
every read and write scope. Each granular write scope also grants its matching
read scope (for example, accounts:write includes accounts:read, but not
balances:read). Batch creation needs both batches:write and
transfers:write; listing Batch Transfers needs both batches:read and
transfers:read.
Keys authenticate API requests, not Relay browser sessions. Current key validity, ownership, scopes and applicable business eligibility are checked on retries too: an idempotency key is not an authorization bypass.
An accepted internal transfer creates one txn_… ID. The source Account sees
a Transfer and the receiving Account sees a Deposit. Addressing another Customer
credits its immutable default Account. Addressing another Account owned by the
same Customer creates an Account transfer with one debit and one credit view.
Accepted outbound money moves from available to reserved. At settlement it
leaves the source balance and is credited to the destination balance. A failed
or voided movement remains visible and the reservation is released.
Amounts are decimal strings in major currency units. New payments must be
positive USD amounts with at most two decimal places and no more than
9999999999999999.99 per payment. JSON numbers, commas, whitespace, scientific
notation and negative values are rejected. Monetary amounts returned in balance
and activity views follow the response schemas; a zero balance is valid.
Send the request key in the Idempotency-Key HTTP header, not in JSON.
Create Account, Recipient, Transfer and Batch require it. Account PATCH,
Recipient PUT and Recipient DELETE accept it optionally; omitting it does not
provide saved-response protection. Keep the same key and input for retries of
one operation. Do not generate a new key merely because the connection timed out.
Keys apply to a Customer and operation, even if the Customer uses another API
key. For transfers, changing between destination.customer_id and
destination.account_id while reusing the same key within 24 hours returns
409 Conflict. For updates and removals, using the same key for a different
resource also returns 409 Conflict.
After a request succeeds, matching retries within 24 hours return the original response:
| Operation | Saved response |
|---|---|
| Create Account | Initial 201 Account body and Location. |
| Update Account | 200 Account body from that update, even after later edits. |
| Create Recipient | Original 201 for a new Recipient, or 200 for an existing or restored one; original body and Location when present. |
| Replace Recipient | 200 Recipient body from that replacement, even after later edits or removal. |
| Remove Recipient | 204, without removing a subsequently re-added Recipient. |
| Create internal Transfer | Initial 201 Transfer body and Location, even after settlement or failure. |
| Create Batch | Initial 202 Batch body and Location, with the original counts. |
Replay does not reapply an edit, restore a removed Recipient or send money again. Use GET for current state. It is not proof of settlement.
Concurrent matching requests wait for the first transaction to finish. If it
commits, they replay its response; being in flight alone is not a conflict.
A changed, valid request under an unexpired idempotency key returns 409 with
details.code: idempotency_conflict and details.param: Idempotency-Key, without
an existing_id. Each operation defines how it normalizes input before comparing
it. Request validation still runs, so invalid input can return 400 instead.
Authentication, authorization and maintenance checks also apply to retries.
Replay does not extend the 24-hour window. At or after expiry, the same key can identify new work, including another payment. Save resource IDs separately and reconcile uncertain outcomes before resubmitting. Use a new key for each new intent, not for a retry of an uncertain one.
List operations use opaque cursors. Pass next_cursor back as cursor with
the same filters and ordering. Do not parse or construct cursors. Stop when
has_more is false. An invalid or mismatched
cursor returns 400; restart from the first page. Pagination is not a frozen
snapshot of resources that continue changing.
Errors use { code, message, details }. The outer code describes the HTTP
error class; details.code, when present, is Pocket's machine-readable reason.
Do not branch on message text. Details may be null for transport/authentication
failures. Preserve unknown reason codes, and record details.request_id when
provided for support.
| HTTP status | Meaning and next step |
|---|---|
400 |
Invalid fields, IDs, filters or cursor; inspect details.param and correct the request. Recipient policy rejection can include per-item failures. |
401 |
Missing, invalid, expired or revoked API credential, or wrong audience. Check the environment and key. |
402 |
Direct Transfer rejected for insufficient available funds (insufficient_funds); no accepted Transfer is returned. |
403 |
Insufficient scopes, Customer eligibility or permission. Retrying the same request does not grant access. |
404 |
Resource missing or not visible to this Customer. Recipient resolution can use recipient_not_found without revealing the private cause. |
409 |
State conflict or live-key input mismatch. Idempotency conflicts use outer aborted, reason idempotency_conflict and parameter Idempotency-Key; no existing_id is supplied. |
429 |
Rate or rolling-send limit (rate_limited). Respect Retry-After when present; no fixed requests-per-second allowance is promised. |
500 |
Internal failure; reconcile an uncertain mutation and retry with the same key within its replay window. |
503 |
Temporary dependency unavailability or maintenance. Back off and use the same intent/key when retrying. |
During maintenance, 503 can instead have details: { "maintenance": true, "retry_after_seconds": 60 }, without a Pocket reason or request ID. Maintenance
headers (X-Pocket-Maintenance, Retry-After) may be present; authentication
can reject the request before those headers are attached. The body remains the
maintenance indicator.
201 Created for a Transfer means it was accepted, not settled. 202 Accepted
for a Batch means the Batch was saved for asynchronous execution, not that its
items succeeded. GET returns current progress; replaying POST returns the
original acceptance response. A terminal Batch can be completed,
partially_completed or failed; reconcile each item, not just the Batch status.
If a response is lost, retry the same request with the same key within 24 hours.
Once you have the resource ID, reconcile using GET and, for a Batch, its items
and resulting Transfers. Retain each item's item_id and Transaction ID, and
use terminal movement status to determine the payment outcome.
If the replay window has elapsed, or the outcome remains uncertain, do not
blindly resubmit with the old or a new key: either can create another payment.
Reconcile saved IDs and activity first, and contact Pocket when an outcome cannot
be established. A reference is a reconciliation label, not a uniqueness
constraint. Do not assume a failed HTTP request proves no money movement was
accepted.
A payroll integration uses an API key for one active, unblocked business Customer. That Customer owns the Recipient roster and the source Accounts, and submits each Batch. Employees are destination Customers, not Accounts or Recipient IDs. Adding a Recipient does not create a Customer.
The normal flow is:
source.customer_id to use its
default Account, or a chosen Account in source.account_id. The resolved
source Account is fixed when the Batch is accepted.Recipient reads require recipients:read; Recipient writes require
recipients:write. Submitting a Batch requires both batches:write and
transfers:write. Reading Batches and their items requires batches:read;
listing a Batch's Transfers also requires transfers:read. Scopes and Customer
eligibility are checked again on retries.
Use the public /v1 endpoints for this integration. The Relay console's CSV
preview/execute workflow and its operator permissions are a separate API
surface; a public Batch submission queues execution directly.
Add each employee to the authenticated business Customer's recipient roster.
The Recipient has its own rcp_… ID for roster management. Its linked
customer_id is the destination to use in a Batch; never send the Recipient ID
as a payment destination.
POST /v1/customers/cus_01j8ma3wv3mepwabfxxs0yc395/recipients
Authorization: Bearer eak_v1_...
Idempotency-Key: add-employee-1042
Content-Type: application/json
{
"customer_id": "cus_01jm0defbbct46vh2ev5dkkhej",
"label": "Taylor Reed",
"reference": "EMP-1042",
"metadata": {"department": "Operations"}
}
Supply a nonblank label and at least one of customer_id, paytag, email,
or phone. If several identifiers are supplied, all must resolve uniquely to
the same existing, active, unblocked Customer other than the source Customer.
Unresolved, conflicting or unavailable matches return the same
privacy-preserving 404 with details.code: recipient_not_found. This
operation does not create pending Recipients or onboard employees. Existing
pending records remain readable, have customer_id: null, and cannot be used
as Batch destinations.
A new Recipient returns 201 Created and a relative Location. An existing
non-removed Recipient returns 200 OK without applying the submitted label,
reference or metadata. A new request to re-add a removed Customer restores the
same Recipient ID with the submitted fields and returns 200 OK.
Creation requires Idempotency-Key. An unchanged retry within 24 hours returns
the saved original response, status and Location where present, even after
later edits or removal. It does not restore or update the Recipient. Use GET
for current state, especially capabilities.can_receive_transfers: a replay's
capability value is a saved snapshot. The current capability checks roster and
Customer eligibility, not wallet readiness, source funds, limits or the final
payment outcome. Batch acceptance and execution perform further checks.
PUT replaces all four mutable fields: label, reference, status and
metadata. Supply reference: null to clear it and metadata: {} to clear
metadata; status may be active or inactive. DELETE soft-removes the
Recipient and returns 204 No Content. Removed records remain readable but
are excluded from unfiltered lists.
PUT and DELETE accept an optional Idempotency-Key. Use one to make a retry
return the original response without reapplying an old edit or deleting a
subsequently restored Recipient. Keys are scoped by business Customer and
operation for 24 hours, not by API-key credential or Recipient ID. Changing the
target or normalized input under an unexpired idempotency key returns 409 with
details.code: idempotency_conflict. Use a new key for a new operation.
Each Account holds USD and has its own Account ID. Accounts belong to the Customer
attached to your API key. A Customer can have more than one Account in the same
currency. Use GET /v1/accounts (accounts:read) to discover them and
GET /v1/accounts/{account_id} to read an Account's label, reference and metadata.
An eligible business Customer can create an additional, non-default Account with
POST /v1/accounts (accounts:write). Pocket creates the default Account; this
API cannot change ownership, currency, default selection or lifecycle status.
Account creation requires an active default Account and its payment setup.
Supplied labels must be unique within the Customer. References are not unique.
PATCH /v1/accounts/{account_id} updates supplied label, reference or
metadata fields. Omitted fields remain unchanged, null clears a label or
reference, and {} replaces metadata with an empty object. At least one field is
required. Creation requires Idempotency-Key; updates support it optionally.
Within 24 hours, matching keyed retries return the original Account
response without reapplying the operation. Read the Account to see later changes.
GET /v1/balances requires balances:read and returns one page of Account
balances, not one aggregated balance per currency:
{
"data": [
{
"account_id": "acc_01j8ma3wv3mepwabfxxs0yc395",
"currency": "USD",
"reference": null,
"available": "2500.00",
"settled": "2500.00",
"reserved": "0.00",
"as_of": "2026-07-31T09:15:00Z"
}
],
"has_more": false,
"next_cursor": null
}
Accounts with no ledger activity are included with zero amounts. settled is the
posted balance; reserved is the amount held for pending outbound movements;
available = settled - reserved. Pending incoming money is not spendable and
does not increase these balances before settlement. Amounts are decimal strings;
do not depend on a fixed number of trailing zeroes.
Account and balance lists accept currency=USD, status, an exact reference,
limit (1–100, default 25) and an opaque cursor. They are ordered newest
Account first, by creation time and Account ID. Keep the same filters and follow
next_cursor while has_more is true. For one current balance, use
GET /v1/accounts/{account_id}/balance with balances:read.
Use /v1/transactions for the combined activity feed, /v1/transfers for
outbound movements and /v1/deposits for inbound movements. Every returned
account_id identifies the exact Account-relative view. A transfer between two
of your Accounts appears twice in the combined feed: one outbound view and one
inbound view sharing the same transaction ID. Another Customer's private
balances and activity are not accessible with your key.
Submit one asynchronous Batch from the authenticated business Customer to its employee Customers:
POST /v1/batches
Authorization: Bearer eak_v1_...
Idempotency-Key: a15e4ae4-52f3-460c-9c1c-5f6b7b90cc44
Content-Type: application/json
{
"source": {
"account_id": "acc_01j8ma3wv3mepwabfxxs0yc395"
},
"recipient_policy": "recipients_only",
"items": [
{
"item_id": "line-001",
"destination": {
"customer_id": "cus_01jm0defbbct46vh2ev5dkkhej"
},
"amount": "100.00",
"currency": "USD",
"reference": "EMP-1042"
}
],
"reference": "northwind-payroll-2026-07"
}
Use a new key for each pay run and keep the same key and input for retries of
that submission. Send 1–4,096 items, each with a unique, case-sensitive,
nonblank item_id of at most 255 characters. Amounts are positive USD decimal
strings with at most two decimal places. Every destination must be another
Customer; Batch items do not accept Account destinations.
recipients_only checks the entire request before acceptance: each destination
must be an active eligible roster member. Pocket also checks that the destination
can receive the payment.
can_receive_transfers: true does not guarantee that the recipient can receive this payment.
A failed pre-check returns 400, details.code: recipient_policy_failed, and
details.failures entries containing item_id, an indexed param, and a
privacy-preserving failure code. No Batch is created. Omitting the policy
selects any_customer, which skips the roster pre-check and checks destination
eligibility and payability when each item first executes.
Success returns 202 Accepted, a Batch body, and
Location: /v1/batches/{batch_id}. Acceptance does not reserve the whole Batch
amount. Each item checks the pinned source Account's available balance and
applicable transfer rules when it runs; processing order is not guaranteed.
The roster policy is not checked again after acceptance. There is no public
cancel endpoint for an accepted Batch.
An unchanged retry within 24 hours returns the saved initial 202 response
and Location, without resubmitting items or refreshing counts. GET the Batch
for progress. A changed request under the same unexpired idempotency key returns 409 with
details.code: idempotency_conflict; it does not include an existing Batch ID.
Retries do not extend the 24-hour window. After expiry the key can create another Batch.
Use GET /v1/batches/{batch_id}/items to reconcile every submitted item_id.
A pending item, or an internal_error item with transfer_id: null, is not
proof that no payment started.
The Transfer list contains only linked, visible Transfers, so an empty list is
not proof that the Batch did nothing. If a submission result is lost, retry
within the replay window using the same input and key. For an older uncertain
result, locate the Batch by its saved ID or list/filter by your reference and
creation time, then reconcile its items and source activity before submitting
anything again. References are not unique and item_id only identifies a line
within one Batch. Escalate an unresolved payment outcome for reconciliation;
do not automatically copy ambiguous lines into a new Batch.
Create an internal transfer from the authenticated Customer to another Pocket
Customer using a key with transfers:write:
POST /v1/transfers/internal
Authorization: Bearer eak_v1_...
Idempotency-Key: d9f80740-7f8e-4f16-b5cc-4fd5bf7d3b21
Content-Type: application/json
{
"source": {
"account_id": "acc_01j8ma3wv3mepwabfxxs0yc395"
},
"destination": {
"customer_id": "cus_01jm0defbbct46vh2ev5dkkhej"
},
"amount": "100.00",
"currency": "USD",
"reference": "payrun-2026-07",
"memo": "July salary"
}
Supply exactly one source selector and one destination selector.
source.customer_id may instead identify the Customer attached to the API key,
in which case Pocket uses its immutable default Account. An explicit source
Account must belong to that Customer. A Customer destination must be a different
Customer; it receives into its default Account. Its private Account ID, balance
and unrelated activity are never exposed to the sender. The destination uses a
Customer ID, not a Recipient relationship ID.
To move money between your own Accounts, set destination.account_id to another
owned Account instead. It must differ from the resolved source Account. You
cannot select another Customer's Account directly.
amount is a positive decimal string in USD, with at most two decimal places.
reference (up to 255 characters) and memo (up to 100) are optional strings,
not null. The reference is private to your Customer and is not a uniqueness
constraint. The destination Customer can see the memo.
Both destination types require the Idempotency-Key header. Matching retries
within 24 hours return the original response. Keep the same key and body when
retrying a payment. Changing between destination.customer_id and
destination.account_id with the same key returns 409 Conflict, as does
changing the amount. Retries return the original 201, Location, status and
timestamps. Reusing an expired key can create another transfer.
201 Created means the payment was accepted. While pending, the source amount
is reserved and its available balance decreases; the destination is not yet
credited. Settlement posts the source debit and destination credit. Failure or
voiding releases the reservation without a destination credit. Use
GET /v1/transfers/{transfer_id} with transfers:read to observe current state.
For an owned-Account move, the transaction feed contains both Account views.
Transfers between your own Accounts are not subject to the 30-day sending limit.
A Customer-to-Customer payment that exceeds this limit returns 429.
Insufficient funds return 402. Both responses reject the new payment.
If a response is lost, retry with the same key within its 24-hour window. After expiry, reconcile known
transaction IDs and references before deciding whether to send again; a reference
alone does not prevent duplicates.
Required scope: accounts:read.
Lists Accounts owned by the authenticated Customer, ordered by
(created_at DESC, id DESC). More than one Account may use the same currency.
All lifecycle states are included unless status filters them. The page
size defaults to 25 and may be 1–100. Follow next_cursor while has_more
is true, retaining the same filters.
| currency | string (Currency) Value: "USD" Example: currency=USD Public settlement currency debited from or credited to a Customer balance.
|
| status | string (AccountStatus) Enum: "active" "suspended" "closed" Account lifecycle state. It is read-only through the public API. |
| reference | string <= 255 characters ^[^\x00]*$ Exact match on your opaque |
| cursor | string <= 8192 characters Opaque cursor ( |
| limit | integer [ 1 .. 100 ] Default: 25 |
{- "has_more": true,
- "next_cursor": "string",
- "data": [
- {
- "id": "acc_01j8ma3wv3mepwabfxxs0yc395",
- "currency": "USD",
- "status": "active",
- "is_default": true,
- "label": "string",
- "reference": "string",
- "metadata": {
- "property1": "string",
- "property2": "string"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
]
}Required scope: accounts:write.
Creates an additional active USD Account. The authenticated Customer must be an eligible business. The immutable default Account is created by Pocket, not through this operation. Creation requires an active default Account with its payment setup. This endpoint does not provision receiving instructions or change the Customer's default routing.
Supplied labels must be unique within the Customer, including the empty
string, and are compared exactly without trimming or case-folding.
References are not unique. Duplicate labels and unusable Account/payment
setup return 409 with details.code: conflict.
Idempotency: The Idempotency-Key HTTP header is required. Keys are
scoped to the authenticated Customer and Account-create operation, not to
the API-key credential. Matching input includes the Customer and the
requested label, reference and metadata. Omitted metadata and {}
have the same request identity. Omitted label/reference and empty strings
are distinct. Reusing an unexpired idempotency key for different input returns 409.
For the default 24-hour window, an unchanged retry returns the saved initial
201 Created Account response and original Location without creating
another Account. Later changes to the Account's label, status, metadata or
timestamps do not change that response. It does not return or overwrite
the Account's current state. Use GET /v1/accounts/{account_id} for current
state. Authentication and authorization apply to every attempt.
Concurrent requests with the same key and input wait for the first attempt to commit successfully, then replay its response. Being in flight alone does not produce an idempotency conflict. Retries do not extend the window. Keys do not permanently identify Accounts: at or after 24-hour expiry, reusing the key can create another Account. Retain the returned Account ID for subsequent operations.
Example request headers:
POST /v1/accounts
Authorization: Bearer eak_v1_...
Idempotency-Key: create-payroll-account-1042
Content-Type: application/json
| Idempotency-Key required | string [ 1 .. 255 ] characters ^[^\x00]*[^\s\x00][^\x00]*$ Example: create-payroll-account-1042 Required, case-sensitive request key. Supply exactly one nonblank value
containing at most 255 Unicode code points, encoded as valid UTF-8 with
no NUL character. Missing, empty, blank, invalid or repeated header values
return |
| label | string <= 255 characters ^[^\x00]*$ Customer-scoped unique label, compared exactly as supplied. |
| reference | string <= 255 characters ^[^\x00]*$ Private, non-unique reference. |
object (AccountMetadata) <= 50 properties Private string metadata for an Account. At most 50 entries. Keys must be nonempty and at most 64 Unicode characters; values may be empty and at most 256 Unicode characters. Neither keys nor values may contain NUL. Values are preserved without trimming, case-folding or normalization. Account validation reports |
{- "label": "Payroll",
- "reference": "payroll-1042",
- "metadata": { }
}{- "id": "acc_01j8ma3wv3mepwabfxxs0yc395",
- "currency": "USD",
- "status": "active",
- "is_default": true,
- "label": "string",
- "reference": "string",
- "metadata": {
- "property1": "string",
- "property2": "string"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Required scope: accounts:read.
Returns the owned Account's current state, including inactive Accounts.
A malformed Account ID returns 400; a missing or unowned Account returns
404. This read is not a replay of an earlier create or update response.
| account_id required | string^acc_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: acc_01j8ma3wv3mepwabfxxs0yc395 An Account owned by the authenticated Customer. |
{- "id": "acc_01j8ma3wv3mepwabfxxs0yc395",
- "currency": "USD",
- "status": "active",
- "is_default": true,
- "label": "string",
- "reference": "string",
- "metadata": {
- "property1": "string",
- "property2": "string"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Required scope: accounts:write.
Replaces supplied presentation fields. Ownership, currency, default selection and lifecycle status are read-only in 0.6.0.
Supply at least one of label, reference or metadata. Omitted fields
remain unchanged; null clears label or reference, and {} clears
metadata. A supplied metadata object replaces the complete map.
Supplied labels must be unique within the Customer, including the empty
string; duplicate labels return 409 conflict. References are not unique.
Idempotency: The optional Idempotency-Key header binds a request for
24 hours. Omitting the header performs an unkeyed update. The body must not
contain idempotency_key.
Keys are scoped to the authenticated Customer and Account-update operation,
not to the API-key credential or target Account. The request identity
includes the Customer, target Account, and supplied presentation fields,
distinguishing omitted fields from explicit clears. Reusing an unexpired idempotency key
for another Account or different patch input returns 409.
An unchanged retry returns the saved successful 200 OK Account response,
including the original updated_at, without applying the update again.
A later change to the Account does not change this saved response. Use
GET /v1/accounts/{account_id} to retrieve current state. Authentication
and authorization still apply to every attempt.
Concurrent requests with the same key and input wait for the first attempt to commit successfully and then replay its response. Being in flight alone does not produce an idempotency conflict. Retries do not extend the window; at or after 24-hour expiry, the key can identify a new update.
| account_id required | string^acc_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: acc_01j8ma3wv3mepwabfxxs0yc395 An Account owned by the authenticated Customer. |
| Idempotency-Key | string [ 1 .. 255 ] characters ^[^\x00]*[^\s\x00][^\x00]*$ Example: update-payroll-account-1042 Optional, case-sensitive request key. Supply exactly one nonblank value
containing at most 255 Unicode code points, encoded as valid UTF-8 with
no NUL character. Empty, blank, invalid or repeated header values return
|
| label required | string or null <= 255 characters ^[^\x00]*$ |
| reference | string or null <= 255 characters ^[^\x00]*$ |
object (AccountMetadata) <= 50 properties Private string metadata for an Account. At most 50 entries. Keys must be nonempty and at most 64 Unicode characters; values may be empty and at most 256 Unicode characters. Neither keys nor values may contain NUL. Values are preserved without trimming, case-folding or normalization. Account validation reports |
{- "label": "string",
- "reference": "string",
- "metadata": {
- "property1": "string",
- "property2": "string"
}
}{- "id": "acc_01j8ma3wv3mepwabfxxs0yc395",
- "currency": "USD",
- "status": "active",
- "is_default": true,
- "label": "string",
- "reference": "string",
- "metadata": {
- "property1": "string",
- "property2": "string"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Required scope: balances:read.
Returns current ledger-derived funds for one owned Account, not a
Customer-wide total. An Account without ledger activity has zero amounts.
A malformed Account ID returns 400; a missing or unowned Account returns
404. as_of is the snapshot time, not a synchronization token.
| account_id required | string^acc_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: acc_01j8ma3wv3mepwabfxxs0yc395 An Account owned by the authenticated Customer. |
{- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395",
- "currency": "USD",
- "reference": null,
- "available": "2500.00",
- "settled": "2500.00",
- "reserved": "0.00",
- "as_of": "2026-07-31T09:15:00Z"
}Required scope: balances:read.
Returns current ledger-derived funds for one owned Account, not a
Customer-wide total. An Account without ledger activity has zero amounts.
A malformed Account ID returns 400; a missing or unowned Account returns
404. as_of is the snapshot time, not a synchronization token.
| account_id required | string^acc_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: acc_01j8ma3wv3mepwabfxxs0yc395 An Account owned by the authenticated Customer. |
{- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395",
- "currency": "USD",
- "reference": null,
- "available": "2500.00",
- "settled": "2500.00",
- "reserved": "0.00",
- "as_of": "2026-07-31T09:15:00Z"
}Required scope: balances:read.
Returns one page of balances for matching Accounts owned by the
authenticated Customer, not an aggregate per currency. Accounts with no
ledger activity are included with zero amounts. All lifecycle states are
included unless status filters them.
Accounts are ordered by (created_at DESC, id DESC). The page size defaults
to 25 and may be 1–100. Follow next_cursor while has_more is true,
retaining the same filters.
| currency | string (Currency) Value: "USD" Example: currency=USD Only balances in this currency. |
| status | string (AccountStatus) Enum: "active" "suspended" "closed" Account lifecycle state. It is read-only through the public API. |
| reference | string <= 255 characters ^[^\x00]*$ Exact match on your opaque |
| cursor | string <= 8192 characters Opaque cursor ( |
| limit | integer [ 1 .. 100 ] Default: 25 |
{- "has_more": true,
- "next_cursor": "string",
- "data": [
- {
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395",
- "currency": "USD",
- "reference": null,
- "available": "2500.00",
- "settled": "2500.00",
- "reserved": "0.00",
- "as_of": "2026-07-31T09:15:00Z"
}
]
}Recipients are business-owned roster resources identified by rcp_…. A pending Recipient
has no matched Customer. A linked Recipient returns the Customer cus_… ID that Transfers
and Batch items use as their destination.
Recipient resources belong to the authenticated business Customer. An authenticated
individual Customer returns 403. Another Customer returns 404 when used as
business_customer_id.
id manages the roster resource; it is never a payment destination.customer_id is the Customer destination for Transfers and Batch
items. It is null while the Recipient is pending.Pocket normalizes identity inputs before matching:
customer_id: exact Pocket cus_… ID.paytag: surrounding whitespace and one optional leading @ removed, then normalized
with Pocket's paytag case rules.phone: E.164 international phone-number format.email: surrounding whitespace trimmed, then compared case-insensitively after Pocket's
email normalization.Supply at least one identifier. When several are supplied, every value must resolve
uniquely to the same Customer. A malformed identifier returns 400 with param
naming the field. Conflicting matches, ambiguity, blocks, and unavailable targets return the
same generic 404 recipient_not_found and create nothing.
Required scope: recipients:write.
Customer eligibility: authenticated business Customer only.
Create a Recipient (rcp_…) on this business's roster. Pocket first attempts to resolve
one or more exact identifiers to an existing Customer. A linked Recipient returns that
Customer's customer_id, which is the destination to use in internal Transfers and Batch
items. The Recipient id manages the roster resource and is never a payment destination.
business_customer_id must identify the authenticated business Customer. Another
Customer returns 404.
Lookup and assurance
Supply at least one of customer_id, exact paytag, email, or phone. You may supply several
identifiers to increase confidence in the match. Every supplied identifier must resolve
uniquely to the same Customer.
Government-ID and name lookup are not supported. Email and phone lookups match the identifiers stored for an existing Customer; the response does not provide identity-verification evidence or an assurance level.
When the identifiers do not resolve completely to one Customer, Pocket returns
404 recipient_not_found and creates nothing. This includes an unknown explicit
customer_id, conflicting matches, ambiguous identity data, blocked relationships, and
unavailable targets. The response does not identify which value failed.
The target must be active, unblocked, and different from the authenticated
business Customer. This operation does not create a Customer or a pending
Recipient. Existing pending Recipient records remain readable.
Recipient data
label, reference, and metadata belong to this business's roster relationship. Pocket
does not compare label with or replace it from the Customer's profile.
Retries and re-adding
The Idempotency-Key HTTP header is required. Keys are scoped to the
authenticated Customer and Recipient-create operation. Matching input
includes the Customer and normalized lookup identifiers, label,
reference and metadata. A changed normalized request with the same live
key returns 409 idempotency_conflict.
For the default 24-hour window, a matching retry returns the first successful
response without executing the operation again. Replay preserves the saved
Recipient body, original HTTP status, and original Location header value
or absence. Later edits or removal do not change the saved body, including
its status, capabilities and timestamps. Replay does not re-add or restore
a removed Recipient. Use GET to retrieve current state.
Concurrent requests with the same key and normalized input wait for the first attempt to commit successfully, then replay its response. Being in flight alone does not produce an idempotency conflict. Retries do not extend the window; at or after 24-hour expiry, the key can identify new work. Authentication and authorization apply to every attempt.
A new linked Recipient returns 201 Created. A request that identifies an existing
non-removed Recipient returns 200 OK without changing its business-owned fields; use
PUT to replace their mutable state. A replay preserves that original 201
or 200 status.
Re-adding a removed Recipient restores it using the same ID and the newly submitted
label, reference and metadata, and returns 200 OK.
Privacy
The response returns the Recipient id and a nullable resolved customer_id, but no Pocket
profile name, email, phone, paytag, government identity, KYC state, onboarding state, or
private capability reason. Raw email and phone are sensitive lookup inputs;
they are not returned on the Recipient or echoed in public lookup errors.
Do not copy them into metadata.
| business_customer_id required | string^cus_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: cus_01j8ma3wv3mepwabfxxs0yc395 The authenticated business Customer whose recipient roster the API manages. An individual Customer returns |
| Idempotency-Key required | string [ 1 .. 255 ] characters ^[^\x00]*[^\s\x00][^\x00]*$ Example: add-employee-1042 Required, case-sensitive request key. Supply exactly one nonblank value
containing at most 255 Unicode code points, encoded as valid UTF-8 with
no NUL character. Missing, empty, blank, invalid or repeated header values
return |
| label required | string [ 1 .. 120 ] characters ^[^\x00]*\S[^\x00]*$ Business-supplied nonblank display label. The length limit is checked before surrounding whitespace is trimmed. |
| customer_id required | string (RecipientCustomerIdLookup) ^cus_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Match by the customer's exact Pocket ID ( |
| paytag | string (RecipientPaytagLookup) [ 1 .. 32 ] characters ^[^\x00]*[^\s\x00][^\x00]*$ Match by exact paytag after trimming surrounding whitespace, removing at
most one leading |
string <email> (RecipientEmailLookup) <= 254 characters ^[^\x00]*$ Match a stored Customer email after trimming surrounding whitespace and lowercasing it. A syntactically valid mailbox address is required; display names are not accepted. This sensitive lookup input is not returned on the Recipient and does not provide verification evidence in the response. | |
| phone | string (RecipientPhoneLookup) ^\+[1-9][0-9]{1,14}$ Match a stored Customer phone using E.164 form (for example, |
| reference | string or null <= 255 characters ^[^\x00]*$ Optional business-owned reconciliation reference. Omitted and null are equivalent; an empty string is preserved and is distinct from null. |
object (Metadata) <= 50 properties Up to 50 caller-defined string key/value pairs. An empty object is valid. Keys
OpenAPI 3.0 cannot express the key-length limit for arbitrary properties, so Pocket enforces
it at runtime. An invalid key returns Values
An overlong value returns Privacy The API shows metadata only in the Customer view where the caller set it. The API does not share metadata with money-movement counterparties. Use it for opaque business identifiers and non-sensitive labels only. Do not include:
Pocket may reject metadata that appears sensitive, but the caller remains responsible for the data it supplies. |
{- "label": "Taylor Reed",
- "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej",
- "reference": "EMP-1042",
- "metadata": {
- "department": "Operations"
}
}{- "id": "rcp_01k2p7m4sd6t8v0x3y5z9a1bcf",
- "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej",
- "label": "Taylor Reed",
- "reference": "EMP-1042",
- "association_status": "linked",
- "status": "active",
- "capabilities": {
- "can_receive_transfers": true
}, - "metadata": {
- "department": "Operations"
}, - "created_at": "2026-07-31T09:05:00Z",
- "updated_at": "2026-07-31T09:05:00Z"
}Required scope: recipients:read.
Customer eligibility: authenticated business Customer only.
List this business's linked and pending Recipient resources. A linked Recipient returns its
destination customer_id; a pending Recipient returns customer_id: null. The API returns
business-owned relationship fields. It does not expose Customer profile, contact, KYC,
onboarding, balance, activity, or payment details.
status=removed to list removed recipients.customer_id.q searches only this business's Recipient ID, label, reference, metadata keys and
values, and linked customer_id; it never searches Pocket Customer identity data.GET /v1/customers/{business_customer_id}/recipients/{recipient_id}.Results are ordered by creation time and Recipient ID. Use order to return the oldest or
newest Recipients first. A cursor is bound to the owning business Customer, order, and
normalized status, association_status, can_receive_transfers, customer_id,
reference, and q filters.
The default is newest first, with limit 25 (allowed range 1–100).
Filters apply before pagination; repeat them with a cursor. The limit may
change. Empty scalar query values are treated as omitted. Results are
current views, not a frozen multi-page snapshot; restart a traversal to
observe records whose status or capability changed.
| business_customer_id required | string^cus_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: cus_01j8ma3wv3mepwabfxxs0yc395 The authenticated business Customer whose recipient roster the API manages. An individual Customer returns |
| status | string (RecipientStatus) Enum: "active" "inactive" "removed" Only recipients in this relationship state. Without this filter, the list excludes removed recipients. |
| association_status | string (RecipientAssociationStatus) Enum: "linked" "pending" "unavailable" Only recipients in this association state. |
| can_receive_transfers | boolean Only recipients with this current advisory roster/Customer capability. Supply exactly |
| customer_id | string^cus_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Only the linked Recipient for this exact Customer ID. Pending Recipients never match. |
| reference | string <= 255 characters ^[^\x00]*$ Exact business-owned recipient reference. |
| q | string [ 1 .. 255 ] characters ^[^\x00]*\S[^\x00]*$ Case-insensitive substring search over this business's label, reference, and metadata keys and values, plus prefix Recipient ID and linked Customer ID matching. SQL pattern characters |
| order | string Default: "desc" Enum: "asc" "desc" Sort by creation time. |
| cursor | string <= 8192 characters Opaque cursor ( |
| limit | integer [ 1 .. 100 ] Default: 25 |
{- "data": [
- {
- "id": "rcp_01k2p7m4sd6t8v0x3y5z9a1bcf",
- "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej",
- "label": "Taylor Reed",
- "reference": "EMP-1042",
- "association_status": "linked",
- "status": "active",
- "capabilities": {
- "can_receive_transfers": true
}, - "metadata": {
- "department": "Operations"
}, - "created_at": "2026-07-31T09:05:00Z",
- "updated_at": "2026-07-31T09:05:00Z"
}
], - "has_more": true,
- "next_cursor": "string"
}Required scope: recipients:read.
Customer eligibility: authenticated business Customer only.
Retrieve this business's linked or pending Recipient by recipient_id. Soft-removed
Recipients are included. The response contains a nullable destination Customer ID and
business-owned relationship fields, not private Customer data or matching inputs.
| business_customer_id required | string^cus_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: cus_01j8ma3wv3mepwabfxxs0yc395 The authenticated business Customer whose recipient roster the API manages. An individual Customer returns |
| recipient_id required | string^rcp_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: rcp_01k2p7m4sd6t8v0x3y5z9a1bcf The Recipient ( |
{- "id": "rcp_01k2p7m4sd6t8v0x3y5z9a1bcf",
- "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej",
- "label": "Taylor Reed",
- "reference": "EMP-1042",
- "association_status": "linked",
- "status": "active",
- "capabilities": {
- "can_receive_transfers": true
}, - "metadata": {
- "department": "Operations"
}, - "created_at": "2026-07-31T09:05:00Z",
- "updated_at": "2026-07-31T09:05:00Z"
}Required scope: recipients:write.
Customer eligibility: authenticated business Customer only.
Replace this business's mutable recipient state. Supply all of label, reference,
status, and metadata.
label is the business's display name for the recipient.reference is required but may be null to clear it.status may be active or inactive.metadata replaces the whole metadata object. Use {} to clear it.Idempotency-Key is an optional HTTP header.This changes only the Recipient resource. It does not change a linked Pocket Customer. A pending Recipient's lookup identifiers cannot be changed in V1.
active means the business considers the recipient active. It does not guarantee
that the customer can receive a transfer.
capabilities.can_receive_transfers as an advisory
roster/Customer eligibility check. It does not check wallet readiness,
source funds, limits or execution-time payment rules.active update cannot bypass private Customer state.409.Idempotency: Omitting the header performs an unkeyed replacement. A
supplied key binds this operation for the default 24-hour window. Keys are
scoped to the authenticated Customer and Recipient-update operation, not
to the API-key credential or target Recipient. Matching input includes the
Customer, target Recipient, and normalized label, reference, status
and metadata. Reusing an unexpired idempotency key for another Recipient or a different
normalized replacement returns 409.
An unchanged retry returns the first successful 200 OK Recipient response,
including its saved status, capabilities and updated_at. Later changes
or removal do not change that response. Replay does not reapply the update
or restore a removed Recipient. Retrieve the Recipient with GET for its
current state. Authentication and authorization apply to every attempt.
Concurrent requests with the same key and normalized input wait for the first attempt to commit successfully, then replay its response. Being in flight alone does not produce an idempotency conflict. Retries do not extend the window; at or after 24-hour expiry, the key can identify a new replacement.
| business_customer_id required | string^cus_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: cus_01j8ma3wv3mepwabfxxs0yc395 The authenticated business Customer whose recipient roster the API manages. An individual Customer returns |
| recipient_id required | string^rcp_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: rcp_01k2p7m4sd6t8v0x3y5z9a1bcf The Recipient ( |
| Idempotency-Key | string [ 1 .. 255 ] characters ^[^\x00]*[^\s\x00][^\x00]*$ Example: update-employee-1042 Optional, case-sensitive request key. Supply exactly one nonblank value
containing at most 255 Unicode code points, encoded as valid UTF-8 with
no NUL character. Empty, blank, invalid or repeated header values return
|
| label required | string [ 1 .. 120 ] characters ^[^\x00]*\S[^\x00]*$ Business-supplied nonblank display label. The length limit is checked before surrounding whitespace is trimmed. |
| reference required | string or null <= 255 characters ^[^\x00]*$ Set to null to clear. |
| status required | string (RecipientWritableStatus) Enum: "active" "inactive" The recipient relationship states a business may request. Setting |
required | object <= 50 properties Whole-object replacement; use an empty object to clear. |
{- "label": "Taylor Reed",
- "reference": "EMP-1042",
- "status": "inactive",
- "metadata": {
- "department": "Operations"
}
}{- "id": "rcp_01k2p7m4sd6t8v0x3y5z9a1bcf",
- "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej",
- "label": "Taylor Reed",
- "reference": "EMP-1042",
- "association_status": "linked",
- "status": "active",
- "capabilities": {
- "can_receive_transfers": true
}, - "metadata": {
- "department": "Operations"
}, - "created_at": "2026-07-31T09:05:00Z",
- "updated_at": "2026-07-31T09:05:00Z"
}Required scope: recipients:write.
Customer eligibility: authenticated business Customer only.
Soft-remove a linked or pending Recipient from this business's roster by recipient_id.
removed and can_receive_transfers becomes false.GET .../recipients?status=removed.Removing a Recipient cancels future pending resolution and does not affect a linked Pocket Customer or payment history.
Idempotency: Omitting the Idempotency-Key header performs an unkeyed
removal. A supplied key binds this operation for the default 24-hour window.
Keys are scoped to the authenticated Customer and Recipient-remove operation,
not to the API-key credential or target Recipient. Matching input includes
the Customer and target Recipient. Reusing an unexpired idempotency key for another Recipient
returns 409.
An unchanged retry returns the saved successful 204 No Content outcome,
with no response body, without executing the removal again. If the Recipient
has since been re-added, that retry leaves the restored relationship intact.
Use a new key to request another removal. Authentication and authorization
apply to every attempt.
Concurrent requests with the same key and target wait for the first attempt to commit successfully, then replay its success. Being in flight alone does not produce an idempotency conflict. Retries do not extend the window; at or after 24-hour expiry, the key can identify a new removal.
| business_customer_id required | string^cus_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: cus_01j8ma3wv3mepwabfxxs0yc395 The authenticated business Customer whose recipient roster the API manages. An individual Customer returns |
| recipient_id required | string^rcp_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: rcp_01k2p7m4sd6t8v0x3y5z9a1bcf The Recipient ( |
| Idempotency-Key | string [ 1 .. 255 ] characters ^[^\x00]*[^\s\x00][^\x00]*$ Example: remove-employee-1042 Optional, case-sensitive request key. Supply exactly one nonblank value
containing at most 255 Unicode code points, encoded as valid UTF-8 with
no NUL character. Empty, blank, invalid or repeated header values return
|
{- "code": "invalid_argument",
- "message": "The request is invalid.",
- "details": {
- "code": "invalid_request",
- "param": "label",
- "request_id": "req_01jj0m9w2k3n5p7q9r1s3t5v7w"
}
}Account-relative money movement visible to the authenticated Customer. Both
transaction endpoints require transactions:read. Each (id, account_id) pair
identifies one view: outbound (kind: transfer) or inbound (kind: deposit).
A transfer between two owned Accounts produces two views with the same txn_…
ID. A transfer to another Customer exposes only your own Account's view and the
counterparty Customer ID, not their Account ID or balance.
GET /v1/transactions supports account_id, kind, repeated method values,
status, amount_min, amount_max, exact reference, creation and settlement
time ranges, order, limit and cursor. Amount and time bounds are inclusive;
settlement-time filters exclude entries without a settlement timestamp. Lists
default to 25 items, newest first; the maximum page size is 100. Use order=asc
for oldest first. Keep the same filters and order when following next_cursor.
An Account filter must refer to an owned Account.
The account_id filter is optional. If supplied, it must identify an existing
Account you own; otherwise the API returns 404. A malformed Account ID returns
400. An owned Account with no matching activity returns an empty page.
GET /v1/transactions/{transaction_id} returns current state. If the transaction
touches two of your Accounts, supply ?account_id=acc_… to select one view;
omitting it returns 400. An inaccessible transaction, or a transaction that
does not touch the selected Account, returns 404.
Accepted internal movements appear as pending before settlement. Their source
and destination views share the terminal status: settled, failed or voided.
Use the corresponding Transfer or Deposit endpoint for its resource view. Card
activity is feed-only and has no Transfer or Deposit resource. Verification
microdeposits and zero-net Account activity are excluded.
Required scope: transactions:read.
List money-movement activity for the authenticated Customer. Recipient Customers' private activity is never included.
Narrow results with account_id, kind, method, status, amount, reference, created
date, or settled date.
When supplied, account_id must identify an Account owned by the authenticated Customer.
An Account filter identifying a nonexistent or unowned Account returns the same privacy-preserving
404, with outer code: not_found and details.code: not_found.
A malformed Account ID returns 400. A valid owned Account with no
matching activity returns an empty page.
Results are ordered by (created_at, id, account_id). Use order=asc for
oldest first or order=desc (the default) for newest first. Page size
defaults to 25 and may be 1–100. Retain the filters and order when following
next_cursor.
Each item is a transaction (txn_…) seen by the authenticated Customer:
(id, account_id) view appears at most once. An Account transfer has two views.kind and id to fetch a transfer or deposit object when one exists.An accepted internal transfer creates the source and destination Account views at once:
kind=transfer and starts pending.kind=deposit and starts pending.settled, failed, or voided together. Each Customer can read only
its owned Account views; a same-Customer transfer has two such views.A rejected transfer request creates no activity entry.
The current response exposes common movement fields, not method-specific
details, FX pricing, payment purpose codes or bank retrieval references.
Read a transaction to retrieve its current status.
If you sent money to a customer, your feed shows your transfer and its status, not the customer's resulting balance or unrelated activity. The feed excludes verification microdeposits, declined card authorizations, and Pocket-internal treasury operations.
| account_id | string^acc_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: account_id=acc_01j8ma3wv3mepwabfxxs0yc395 Filter an activity list to an Account owned by the authenticated Customer.
|
| kind | string (TransactionKind) Enum: "transfer" "deposit" Example: kind=deposit Only transfers (outbound) or only deposits (inbound). |
| method | Array of strings (Method) Items Enum: "internal" "ach" "wire" "onchain" "ibft" "swift" "faster_payments" "payment_link" "card" Example: method=ach&method=wire Filter transactions by method. Repeat the parameter to match more than one (e.g. |
| status | string (TransactionStatus) Enum: "pending" "settled" "failed" "voided"
|
| amount_min | string (MoneyAmount) <= 19 characters ^(?:0(?:\.[0-9]{1,2})?|[1-9][0-9]{0,15}(?:\.[... Example: amount_min=100.00 Minimum amount (inclusive), as a decimal string in major units, e.g. |
| amount_max | string (MoneyAmount) <= 19 characters ^(?:0(?:\.[0-9]{1,2})?|[1-9][0-9]{0,15}(?:\.[... Example: amount_max=100.00 Maximum amount (inclusive), as a decimal string in major units, e.g. |
| reference | string <= 255 characters ^[^\x00]*$ Exact match on your opaque |
| created_after | string <date-time> Example: created_after=2026-07-31T09:00:00Z Only items created at/after this time (inclusive). |
| created_before | string <date-time> Example: created_before=2026-07-31T09:00:00Z Only items created at/before this time (inclusive). |
| settled_after | string <date-time> Example: settled_after=2026-07-31T09:00:00Z Only items settled at/after this time (inclusive). This filter excludes pending, failed, and voided items because they have no settlement time. |
| settled_before | string <date-time> Example: settled_before=2026-07-31T09:00:00Z Only items settled at/before this time (inclusive). This filter excludes pending, failed, and voided items because they have no settlement time. |
| order | string Default: "desc" Enum: "asc" "desc" Sort by creation time. |
| cursor | string <= 8192 characters Opaque cursor ( |
| limit | integer [ 1 .. 100 ] Default: 25 |
{- "has_more": true,
- "next_cursor": "string",
- "data": [
- {
- "id": "txn_01jjftqbz0n1vzey0pqrq3qmnn",
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395",
- "kind": "deposit",
- "method": "ach",
- "source": null,
- "destination": {
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395"
}, - "amount": "2500.00",
- "currency": "USD",
- "status": "settled",
- "reference": null,
- "memo": null,
- "created_at": "2026-07-30T14:10:00Z",
- "settled_at": "2026-07-31T09:00:00Z"
}
]
}Required scope: transactions:read.
Fetch one transaction entry by ID, e.g. /v1/transactions/txn_….
The endpoint returns the authenticated Customer's view. A transaction that is not visible
to that Customer returns 404.
A same-Customer Account transfer has two owned views. Supply account_id
to select one; omitting it when multiple views exist returns 400 with
details.param: account_id. An Account that the transaction does not touch,
or that the caller does not own, returns 404, not an empty page.
| transaction_id required | string^txn_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: txn_01jjftqbz0n1vzey0pqrq3qmnn |
| account_id | string^acc_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: account_id=acc_01j8ma3wv3mepwabfxxs0yc395 Select one owned Account's viewpoint of this transaction.
|
{- "id": "txn_01jjftqbz0n1vzey0pqrq3qmnn",
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395",
- "kind": "deposit",
- "method": "ach",
- "source": null,
- "destination": {
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395"
}, - "amount": "2500.00",
- "currency": "USD",
- "status": "settled",
- "reference": null,
- "memo": null,
- "created_at": "2026-07-30T14:10:00Z",
- "settled_at": "2026-07-31T09:00:00Z"
}Money leaving one of the authenticated Customer's Accounts. V1 creates internal transfers to another owned Account or another Pocket Customer. Transfer reads also expose supported outbound activity already processed by Pocket; this does not enable ACH, wire, onchain or IBFT creation through Relay. Card spends appear only in the transaction feed.
Reads require transfers:read; internal transfer creation requires
transfers:write. Creation is not restricted to business Customers by this
operation, but the source credential, Customer and Accounts must be eligible.
Customer-to-Customer transfers may have a 30-day sending limit. Transfers between
your own Accounts are not subject to this limit.
GET /v1/transfers accepts repeated method values, source_account_id,
destination_id, status, amount and timestamp bounds, exact reference,
order, limit and cursor. A destination filter can identify an owned Account,
a counterparty Customer, or an external instrument visible on a transfer.
Unowned Account filters return 404; an unmatched Customer or instrument filter
returns an empty page. Historical transfers without an instrument ID cannot
match that instrument filter. Page size defaults to 25 (maximum 100), newest
first. Retain filters and order when following the returned cursor.
GET /v1/transfers/{transfer_id} returns the current outbound view, including
its exact source Account, status and transition timestamps. For an internal
movement, the same transaction ID identifies the destination's Deposit view.
For an owned-Account transfer, both views are accessible to the same Customer.
Otherwise, the destination Customer's private activity remains hidden.
Creation requires Idempotency-Key and returns 201 with a relative Location.
For 24 hours, an unchanged retry replays the saved initial response; it does not
refresh status or send money again. A changed request using an unexpired idempotency key returns
409. Reads return current state. A created, pending transfer is accepted, not
settled. Preserve its ID and poll the read endpoint to reconcile the outcome.
The Transfer response does not expose method-specific details or FX data.
Required scope: transfers:read.
Outbound money movements from the authenticated Customer.
Narrow results with method, source_account_id, destination_id, status, amount,
reference, created date, or settled date.
When supplied, source_account_id must identify an owned Account.
destination_id may identify another owned Account, a Pocket Customer or
an external instrument visible on a transfer. An Account filter identifying a nonexistent or unowned Account
returns 404. An unmatched Customer or instrument filter returns an
empty page, without revealing whether the counterparty exists. Historical
activity without an instrument ID is not matched by that instrument filter.
Results are ordered by creation time and Transfer ID. Use order to return the oldest or
newest Transfers first.
Transfer reads expose supported historical outbound activity; they do not enable ACH, wire, onchain or IBFT creation through Relay. Only internal transfer creation is implemented in this release.
| method | Array of strings (TransferMethod) Items Enum: "internal" "ach" "wire" "onchain" "ibft" Example: method=ach&method=wire Filter transfers by method. Repeat the parameter to match more than one (e.g.
|
| source_account_id | string^acc_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Filter by an Account owned by the authenticated Customer. An Account that does not exist or belongs to another Customer returns privacy-preserving |
| destination_id | string^(?:acc_|cus_|epi_)[0-7][0-9abcdefghjkmnpqrst... Filter by an owned Account, a Pocket Customer, or an external instrument already visible on a matching transfer.
|
| status | string (TransferStatus) Enum: "pending" "settled" "failed" "voided"
|
| amount_min | string (MoneyAmount) <= 19 characters ^(?:0(?:\.[0-9]{1,2})?|[1-9][0-9]{0,15}(?:\.[... Example: amount_min=100.00 Minimum amount (inclusive), as a decimal string in major units, e.g. |
| amount_max | string (MoneyAmount) <= 19 characters ^(?:0(?:\.[0-9]{1,2})?|[1-9][0-9]{0,15}(?:\.[... Example: amount_max=100.00 Maximum amount (inclusive), as a decimal string in major units, e.g. |
| reference | string <= 255 characters ^[^\x00]*$ Exact match on your opaque |
| created_after | string <date-time> Example: created_after=2026-07-31T09:00:00Z Only items created at/after this time (inclusive). |
| created_before | string <date-time> Example: created_before=2026-07-31T09:00:00Z Only items created at/before this time (inclusive). |
| settled_after | string <date-time> Example: settled_after=2026-07-31T09:00:00Z Only items settled at/after this time (inclusive). This filter excludes pending, failed, and voided items because they have no settlement time. |
| settled_before | string <date-time> Example: settled_before=2026-07-31T09:00:00Z Only items settled at/before this time (inclusive). This filter excludes pending, failed, and voided items because they have no settlement time. |
| order | string Default: "desc" Enum: "asc" "desc" Sort by creation time. |
| cursor | string <= 8192 characters Opaque cursor ( |
| limit | integer [ 1 .. 100 ] Default: 25 |
{- "has_more": true,
- "next_cursor": "string",
- "data": [
- {
- "id": "txn_01k0d2f6h8j4m7n9p3q5r1s0vw",
- "method": "internal",
- "reference": "payrun-2026-07",
- "memo": "July salary",
- "source": {
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395"
}, - "destination": {
- "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej"
}, - "batch_id": null,
- "amount": "100.00",
- "currency": "USD",
- "status": "pending",
- "transitioned_at": {
- "pending": "2026-07-31T11:00:00Z",
- "settled": null,
- "failed": null,
- "voided": null
}, - "created_at": "2026-07-31T11:00:00Z",
- "updated_at": "2026-07-31T11:00:00Z"
}
]
}Required scope: transfers:write.
Move funds from the authenticated Customer's default Account (source.customer_id) or an
exact owned Account (source.account_id) either to another Customer
(destination.customer_id) or another Account owned by the same Customer
(destination.account_id). A Customer destination receives into its immutable default
Account. The counterparty Customer's private balance and activity remain hidden.
Supply exactly one source and one destination selector. A Customer source must identify the authenticated Customer. A Customer destination must be another Customer, not a Recipient relationship ID. An Account destination must be owned by the authenticated Customer and differ from the resolved source Account. Selecting another Customer's Account directly is not supported.
Individuals and businesses can use this operation. Customer-to-Customer transfers may have 30-day sending limits. Transfers between your own Accounts are not subject to these limits.
Idempotency: The Idempotency-Key HTTP header is required for both
Account-to-Account and Customer-to-Customer P2P transfers. Keys are scoped
to the authenticated Customer and executing transfer operation, not to the
API-key credential. Account transfers and P2P share one operation scope.
Matching input includes the requested source and
destination, normalized amount, currency, reference and memo. Decimal-equivalent
amounts such as "1", "1.0" and "1.00" match. Reusing an unexpired idempotency key for
different input within that operation returns 409. A default-Customer
source selector and an explicit Account selector remain different input,
even if they currently resolve to the same Account.
For the default 24-hour window, an unchanged retry returns the saved initial
201 Created Transfer response and original Location. Replay preserves
the saved Account routing, status, reference, memo and timestamps even if
the Transfer later settles, fails or is voided. It does not move money again
or reload the Transfer's current state. Use GET /v1/transfers/{transfer_id}
for current state. Authentication and authorization apply to every attempt.
Concurrent requests with the same key and input wait for the first attempt to commit successfully, then replay its response. Being in flight alone does not produce an idempotency conflict. Retries do not extend the window; at or after 24-hour expiry, reusing the key can create another transfer. Keep one key and unchanged body for one payment intent. Use a new key for a different intent, including changing between Account-transfer and P2P destinations.
At acceptance
201 Created confirms acceptance, not settlement.
kind is transfer, method is internal, and status is pending.kind is deposit, method is internal, and status is pending.available decreases and reserved increases.At settlement
Both views become settled. The source reservation posts out. Pocket credits the destination
settled and available amounts.
On failure or voiding
Both views remain visible with the terminal status. Pocket releases the source reservation and never credits the destination.
If the API rejects a request before it creates a txn_…, it produces no transfer, deposit,
or transaction resource.
A credential without permission to send returns 403 forbidden. A
destination Customer without a payable default Account returns
403 recipient_not_payable, without revealing private setup details.
A missing, unowned or inactive P2P source Account returns 404. For an
owned-Account transfer, an explicitly selected inactive source or destination
Account returns 409 conflict; missing or unowned Accounts return 404.
A P2P request that exceeds the source Customer's 30-day sending limit returns
429, with outer code: resource_exhausted and details.code: rate_limited.
It creates no transfer, deposit or transaction. This is a rejected request,
not the limit_exceeded failure code on a Batch item.
If a response is lost, retry the original key and request while the key is live. Once expired, reconcile known transaction IDs and references before deciding whether to send again. A reference is not unique and does not prevent another transfer.
| Idempotency-Key required | string [ 1 .. 255 ] characters ^[^\x00]*[^\s\x00][^\x00]*$ Example: d9f80740-7f8e-4f16-b5cc-4fd5bf7d3b21 Required, case-sensitive request key. Supply exactly one nonblank value
containing at most 255 Unicode code points, encoded as valid UTF-8 with
no NUL character. Missing, empty, blank, invalid or repeated header values
return |
required | CustomerReference (object) or AccountReference (object) (AccountSource) The Account that funds an internal Transfer or Batch. Supply exactly one:
|
required | object (InternalTransferDestination) = 1 properties Exactly one destination: another Customer's immutable default Account, or another Account owned by the authenticated Customer. A Customer destination must differ from the authenticated Customer. An Account destination must differ from the resolved source Account. Recipient relationship IDs and another Customer's explicit Account IDs are not supported. |
| amount required | string (WriteAmount) <= 19 characters ^(?:0\.(?:0[1-9]|[1-9][0-9]?)|[1-9][0-9]{0,15... A positive amount to move, expressed as a JSON decimal string in major units. For example,
An invalid, non-positive or overflowing amount returns |
| currency required | string (Currency) Value: "USD" Public settlement currency debited from or credited to a Customer balance.
|
| reference | string <= 255 characters ^[^\x00]*$ Your private external reference for this transfer (e.g. a pay-run or employee ID). Optional, not unique. Filter by it. Counterparties do not see it. |
| memo | string <= 100 characters ^[^\x00]*$ Payment note carried on the movement and visible to the destination customer when the method supports a note. Internal transfer memos can be up to 100 characters. The API rejects longer values with |
{- "source": {
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395"
}, - "destination": {
- "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej"
}, - "amount": "100.00",
- "currency": "USD",
- "reference": "payrun-2026-07",
- "memo": "July salary"
}{- "id": "txn_01k0d2f6h8j4m7n9p3q5r1s0vw",
- "method": "internal",
- "reference": "payrun-2026-07",
- "memo": "July salary",
- "source": {
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395"
}, - "destination": {
- "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej"
}, - "batch_id": null,
- "amount": "100.00",
- "currency": "USD",
- "status": "pending",
- "transitioned_at": {
- "pending": "2026-07-31T11:00:00Z",
- "settled": null,
- "failed": null,
- "voided": null
}, - "created_at": "2026-07-31T11:00:00Z",
- "updated_at": "2026-07-31T11:00:00Z"
}Required scope: transfers:read.
Fetch one transfer by ID, e.g. /v1/transfers/txn_….
This endpoint returns the outbound side when its source Customer is the authenticated
Customer. Otherwise it returns 404.
For an internal movement, the same txn_… may also be retrievable from
GET /v1/deposits/{deposit_id} by the destination Customer. The transfer is retrievable as
soon as its txn_… exists and remains
retrievable through its terminal status.
When both Accounts belong to this Customer, the Transfer and Deposit endpoints select the outbound and inbound views respectively without an additional Account query parameter. Card activity has no Transfer resource.
| transfer_id required | string^txn_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: txn_01k0d2f6h8j4m7n9p3q5r1s0vw |
{- "id": "txn_01k0d2f6h8j4m7n9p3q5r1s0vw",
- "method": "internal",
- "reference": "payrun-2026-07",
- "memo": "July salary",
- "source": {
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395"
}, - "destination": {
- "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej"
}, - "batch_id": null,
- "amount": "100.00",
- "currency": "USD",
- "status": "pending",
- "transitioned_at": {
- "pending": "2026-07-31T11:00:00Z",
- "settled": null,
- "failed": null,
- "voided": null
}, - "created_at": "2026-07-31T11:00:00Z",
- "updated_at": "2026-07-31T11:00:00Z"
}Money arriving in an Account owned by the authenticated Customer. Both Deposit
endpoints require deposits:read. Deposits are read-only: Pocket creates them
from incoming payments and the destination side of an internal transfer. Card
refunds are transaction-feed entries, not Deposit resources.
GET /v1/deposits accepts repeated method values, account_id, status,
amount and timestamp bounds, exact reference, order, limit and cursor.
Page size defaults to 25 (maximum 100), newest first. Amount and time bounds are
inclusive; settlement-time filters exclude entries without a settlement time.
An Account filter must identify an owned Account. Keep the same filters and
order when following next_cursor.
GET /v1/deposits/{deposit_id} returns the current inbound view. The top-level
account_id and destination.account_id identify the same Account. source can
be null when the external sender has no visible Pocket resource. For an
internal payment from another Customer, their Customer ID and memo can be
visible, but their private reference is not. Moving money between your own
Accounts preserves your reference in both views.
An accepted internal transfer creates a pending Deposit with the same txn_…
ID as the source Transfer. It does not increase destination funds before
settlement. Failure or voiding leaves the Deposit visible with the terminal
status and no destination credit. Fetching a Deposit outside your Customer's
visible inbound activity returns 404.
The response reports the Account's USD movement amount and common movement fields. It does not expose method-specific details, original foreign amounts or fee breakdowns.
Required scope: deposits:read.
Inbound money movements into Accounts owned by the authenticated Customer.
Narrow results with method, account_id, status, amount, reference,
created date, or settled date.
An internal transfer appears in the destination Customer's results as soon as its txn_…
identifier exists.
For an internal movement:
pending.When supplied, account_id must identify an Account owned by the authenticated
Customer. An Account filter identifying a nonexistent or unowned Account returns 404.
Results are ordered by creation time and Deposit ID. Use order to return the oldest or
newest Deposits first.
| method | Array of strings (DepositMethod) Items Enum: "internal" "ach" "wire" "onchain" "swift" "faster_payments" "payment_link" Example: method=ach&method=wire Filter deposits by method. Repeat the parameter to match more than one (e.g.
|
| account_id | string^acc_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: account_id=acc_01j8ma3wv3mepwabfxxs0yc395 Filter an activity list to an Account owned by the authenticated Customer.
|
| status | string (TransactionStatus) Enum: "pending" "settled" "failed" "voided"
|
| amount_min | string (MoneyAmount) <= 19 characters ^(?:0(?:\.[0-9]{1,2})?|[1-9][0-9]{0,15}(?:\.[... Example: amount_min=100.00 Minimum amount (inclusive), as a decimal string in major units, e.g. |
| amount_max | string (MoneyAmount) <= 19 characters ^(?:0(?:\.[0-9]{1,2})?|[1-9][0-9]{0,15}(?:\.[... Example: amount_max=100.00 Maximum amount (inclusive), as a decimal string in major units, e.g. |
| reference | string <= 255 characters ^[^\x00]*$ Exact match on your opaque |
| created_after | string <date-time> Example: created_after=2026-07-31T09:00:00Z Only items created at/after this time (inclusive). |
| created_before | string <date-time> Example: created_before=2026-07-31T09:00:00Z Only items created at/before this time (inclusive). |
| settled_after | string <date-time> Example: settled_after=2026-07-31T09:00:00Z Only items settled at/after this time (inclusive). This filter excludes pending, failed, and voided items because they have no settlement time. |
| settled_before | string <date-time> Example: settled_before=2026-07-31T09:00:00Z Only items settled at/before this time (inclusive). This filter excludes pending, failed, and voided items because they have no settlement time. |
| order | string Default: "desc" Enum: "asc" "desc" Sort by creation time. |
| cursor | string <= 8192 characters Opaque cursor ( |
| limit | integer [ 1 .. 100 ] Default: 25 |
{- "has_more": true,
- "next_cursor": "string",
- "data": [
- {
- "id": "txn_01jjftqbz0n1vzey0pqrq3qmnn",
- "method": "ach",
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395",
- "source": null,
- "destination": {
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395"
}, - "reference": null,
- "memo": null,
- "amount": "2500.00",
- "currency": "USD",
- "status": "settled",
- "created_at": "2026-07-30T14:10:00Z",
- "settled_at": "2026-07-31T09:00:00Z",
- "updated_at": "2026-07-31T09:00:00Z"
}
]
}Required scope: deposits:read.
Fetch one deposit by ID, e.g. /v1/deposits/txn_….
This endpoint returns the inbound side when its destination Customer is the authenticated
Customer. Otherwise it returns 404.
For an internal movement:
txn_… may resolve at GET /v1/transfers/{transfer_id} for the source Customer.pending.failed or voided.| deposit_id required | string^txn_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: txn_01jjftqbz0n1vzey0pqrq3qmnn |
{- "id": "txn_01jjftqbz0n1vzey0pqrq3qmnn",
- "method": "ach",
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395",
- "source": null,
- "destination": {
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395"
}, - "reference": null,
- "memo": null,
- "amount": "2500.00",
- "currency": "USD",
- "status": "settled",
- "created_at": "2026-07-30T14:10:00Z",
- "settled_at": "2026-07-31T09:00:00Z",
- "updated_at": "2026-07-31T09:00:00Z"
}An asynchronous group of internal transfers from one authenticated business Customer to destination Customers. Every item has its own status and may fail without changing the result of another item.
The public API supports USD Batch submission, retrieval, Batch listing, item listing and listing the Transfers created by a Batch. POST accepts and queues the work in one operation; it is not the Relay console's CSV preview/execute workflow. It does not reserve funds, guarantee settlement, guarantee execution order, or offer cancellation.
Use Batch counts and item states to track progress. pending means every item
is still pending, not necessarily that processing has not started. processing
means some items have finished and others remain pending. Terminal states are
completed (all settled), partially_completed (some settled), or failed
(none settled). An individual item is pending, settled, failed or
voided.
Lists return data, has_more and next_cursor; the final cursor is null.
The default limit is 25 and the maximum is 100. Batch lists default to newest
first and accept order=asc; item lists always use (created_at ASC, item_id ASC), and Transfer lists use (created_at ASC, id ASC). Repeat the same filters
when using a cursor; the page size may change. Results are current views, not a
frozen multi-page snapshot. When reconciling items whose status may have changed,
start again from the first page using the same filters.
Send an Idempotency-Key header. Matching retries within 24 hours return the
original response. Reads return current state.
Pocket retries each batch payment using the same idempotency key. If its outcome remains unknown after 24 hours, Pocket stops automatic retries to avoid duplicate payments.
Keep the Batch ID and your submitted item IDs. An item_id only identifies a
payment within its Batch. Check uncertain payment outcomes before submitting a
replacement Batch; reusing an item_id in a new Batch can pay the recipient again.
Required scopes: batches:write and transfers:write.
Customer eligibility: authenticated business Customer only.
Submit internal transfers from the authenticated business Customer's default Account
(source.customer_id) or an exact owned Account (source.account_id) asynchronously.
Acceptance does not move or reserve money. An ineligible source returns 403, and the API
creates no batch.
Batch items currently support only USD, funded from the selected USD
Account. Another currency returns 400 invalid_request with
param=items[n].currency, where n is the zero-based item index.
Items accept destination Customer IDs, not Account or Recipient IDs.
Recipient policy pre-check
recipient_policy defaults to any_customer. Set it to recipients_only to check the
complete batch against the authenticated Customer's recipient list before acceptance:
customer_id equals every
destination.customer_id.capabilities.can_receive_transfers set to
true.can_receive_transfers: true does not guarantee that the recipient can receive this payment.This check runs after ordinary validation, including duplicate item_id validation. If any
item fails, one 400 recipient_policy_failed response lists all failures in request order.
A rejected first attempt creates no Batch and saves no key binding. Resolve
the reported policy failures before resubmitting; a new key is not required.
The roster policy is not repeated during item processing. Ordinary transfer
rules, including available funds, send limits and payability, still apply.
With any_customer, destination eligibility and payability are checked
when each item first executes rather than by this acceptance pre-check.
Track every submitted line with GET /v1/batches/{batch_id}/items.
transfer_id: null.txn_…, transfer_id identifies it through
settlement, failure or voiding. An item awaiting a confirmed payment
result can also have transfer_id: null.failure_code: insufficient_funds.item_id in another Batch can pay the recipient again.Duplicate item ids
Every item_id must be unique within items.
reference and the Idempotency-Key header do not identify batch lines.Pocket reports the first repeated value. The request returns 400 invalid_request, with
param set to the second occurrence (items[n].item_id). The index is zero-based.
A rejected first attempt creates no Batch and saves no key binding. Correct the duplicate item ID and resubmit; this validation error does not require a new key.
You cannot cancel a batch after acceptance.
Idempotency: The Idempotency-Key header is required. Keys are scoped
to the authenticated Customer and Batch-create operation. Matching input
includes the requested source, recipient policy, ordered items, reference
and metadata. The first successful response is saved atomically with the
Batch, items and execution messages. An unchanged retry within 24 hours
returns that original 202 Accepted body and Location, not current
counts or payment state. It does not enqueue the items again or recheck
Recipient policy. Caller authorization still applies on replay.
Concurrent matching retries wait for the first commit. Changed input
returns 409 with outer aborted, details.code: idempotency_conflict
and details.param: Idempotency-Key, without existing_id. Expired keys
can create another Batch. Use the saved Batch ID to retrieve an ambiguous
older request, or list by reference and creation time, then reconcile its
items and source activity. References are not unique. Do not submit a
replacement merely because the acceptance response or Transfer link is missing.
Pocket retries each batch payment using the same idempotency key. If its
outcome remains unknown after 24 hours, Pocket stops automatic retries to
avoid duplicate payments. Check uncertain payment outcomes before submitting
a replacement Batch. An item_id identifies a payment within one Batch;
reusing it in a new Batch can pay the recipient again.
| Idempotency-Key required | string [ 1 .. 255 ] characters ^[^\x00]*[^\s\x00][^\x00]*$ Required, case-sensitive request key. Supply exactly one nonblank value
containing at most 255 Unicode code points, encoded as valid UTF-8 with
no NUL character. Missing, blank, repeated or invalid keys return |
required | CustomerReference (object) or AccountReference (object) (AccountSource) The Account that funds an internal Transfer or Batch. Supply exactly one:
|
| recipient_policy | string (BatchRecipientPolicy) Default: "any_customer" Enum: "any_customer" "recipients_only" One-time destination pre-check applied before Pocket accepts a batch.
The roster policy is not evaluated again after acceptance or as individual
Transfers execute. Ordinary funds, limit and payability rules still apply.
Omitting the field and sending |
required | Array of objects (BatchTransferItem) [ 1 .. 4096 ] items Each |
| reference | string <= 255 characters ^[^\x00]*$ Your external reference for this Batch (e.g. a pay-run ID). Optional, non-null, filterable and not unique. An empty string is normalized to absent and returned as null. |
object (BatchMetadata) <= 50 properties Batch and Batch-item metadata: up to 50 string key/value pairs. Keys contain 1–64 Unicode code points and values 0–255. Both must be valid UTF-8 without NUL and are preserved exactly without trimming or case normalization. Key-length limits are enforced at runtime. Omit request metadata to use |
{- "source": {
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395"
}, - "recipient_policy": "recipients_only",
- "items": [
- {
- "item_id": "line-001",
- "destination": {
- "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej"
}, - "amount": "100.00",
- "currency": "USD",
- "reference": "EMP-1042"
}, - {
- "item_id": "line-002",
- "destination": {
- "customer_id": "cus_01j7qkfn0n4egp6wj30vajew7g"
}, - "amount": "120.00",
- "currency": "USD",
- "reference": "EMP-1089"
}
], - "reference": "northwind-payroll-2026-07",
- "metadata": {
- "pay_period": "2026-07"
}
}{- "id": "batch_01j5gr81vpavng9emxaepf2be3",
- "reference": "northwind-payroll-2026-07",
- "source": {
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395"
}, - "recipient_policy": "recipients_only",
- "status": "pending",
- "item_counts": {
- "total": 2,
- "pending": 2,
- "settled": 0,
- "failed": 0,
- "voided": 0
}, - "amount_totals": [
- {
- "currency": "USD",
- "total": "220.00",
- "pending": "220.00",
- "settled": "0.00",
- "failed": "0.00",
- "voided": "0.00"
}
], - "metadata": {
- "pay_period": "2026-07"
}, - "created_at": "2026-07-31T10:00:00Z",
- "finalized_at": null
}Required scope: batches:read.
Customer eligibility: authenticated business Customer only.
List batches submitted by the authenticated Customer.
source_account_id filters only this Customer's Batches. A syntactically
valid Account ID with no matching visible Batches returns an empty page,
without disclosing whether that Account exists or who owns it.
Results use (created_at, id), newest first by default; order=asc
selects oldest first. Filters apply before pagination. A cursor binds
Customer, Account/status/reference/time filters and order, not limit.
Repeat the same filters on continuation; limit may change (default 25,
range 1–100). Empty scalar queries are treated as omitted. Date bounds
are inclusive and created_after must not be later than created_before.
Results are current views, not a frozen multi-page snapshot.
| source_account_id | string^acc_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Filter this Customer's Batches by exact source Account ID. A malformed
or zero ID returns |
| status | string (BatchStatus) Enum: "pending" "processing" "completed" "partially_completed" "failed" Summary status derived from the batch item counts:
Counts describe recorded item states, not independent proof of payment
outcomes. Reconcile an uncertain |
| created_after | string <date-time> Example: created_after=2026-07-31T09:00:00Z Only items created at/after this time (inclusive). |
| created_before | string <date-time> Example: created_before=2026-07-31T09:00:00Z Only items created at/before this time (inclusive). |
| reference | string <= 255 characters ^[^\x00]*$ Exact, case-sensitive reference filter. References are not unique; matches are paginated. Whitespace is retained; an empty query value is treated as omitted. |
| order | string Default: "desc" Enum: "asc" "desc" Sort by creation time. |
| cursor | string <= 8192 characters Opaque cursor ( |
| limit | integer [ 1 .. 100 ] Default: 25 |
{- "has_more": true,
- "next_cursor": "string",
- "data": [
- {
- "id": "batch_01j5gr81vpavng9emxaepf2be3",
- "reference": "northwind-payroll-2026-07",
- "source": {
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395"
}, - "recipient_policy": "recipients_only",
- "status": "pending",
- "item_counts": {
- "total": 2,
- "pending": 2,
- "settled": 0,
- "failed": 0,
- "voided": 0
}, - "amount_totals": [
- {
- "currency": "USD",
- "total": "220.00",
- "pending": "220.00",
- "settled": "0.00",
- "failed": "0.00",
- "voided": "0.00"
}
], - "metadata": {
- "pay_period": "2026-07"
}, - "created_at": "2026-07-31T10:00:00Z",
- "finalized_at": null
}
]
}Required scope: batches:read.
Customer eligibility: authenticated business Customer only.
Retrieve a Batch by ID. Use its current item counts and amount totals to
track progress; this is not the saved initial POST response. A missing
or unowned Batch returns 404; a malformed Batch ID returns 400 with
details.param: batch_id.
| batch_id required | string^batch_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ Example: batch_01j5gr81vpavng9emxaepf2be3 |
{- "id": "batch_01j5gr81vpavng9emxaepf2be3",
- "reference": "northwind-payroll-2026-07",
- "source": {
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395"
}, - "recipient_policy": "recipients_only",
- "status": "pending",
- "item_counts": {
- "total": 2,
- "pending": 2,
- "settled": 0,
- "failed": 0,
- "voided": 0
}, - "amount_totals": [
- {
- "currency": "USD",
- "total": "220.00",
- "pending": "220.00",
- "settled": "0.00",
- "failed": "0.00",
- "voided": "0.00"
}
], - "metadata": {
- "pay_period": "2026-07"
}, - "created_at": "2026-07-31T10:00:00Z",
- "finalized_at": null
}Required scope: batches:read.
Customer eligibility: authenticated business Customer only.
List every submitted item in the batch, including items that failed before creating a transfer.
Use item_id to reconcile the response with the line you submitted. When an item creates
a linked Transfer, transfer_id identifies the resulting txn_….
Items awaiting a confirmed payment result also have transfer_id: null; that value alone is
not proof of a known failure or that no payment started. Available
failure details are in failure_code and failure_message; these fields
are present as null for healthy and voided items.
For retries and payments with an unknown outcome, see Batch Transfers.
Narrow with item_id, status, or reference.
Reconcile uncertain or internal_error outcomes against source activity
before submitting a replacement. A new Batch can create new Transfers.
item_id does not deduplicate across Batches, so resubmitting a settled
or ambiguous line can pay the same Customer twice.
The API orders results by (created_at ASC, item_id ASC).
Filters apply before pagination. A cursor binds Customer, Batch and
item/status/reference filters, not limit (default 25, range 1–100).
Repeat the filters with the cursor. A traversal is not a frozen snapshot;
restart it to observe items that changed status. Empty scalar query
values are treated as omitted.
| batch_id required | string^batch_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ |
| item_id | string [ 1 .. 255 ] characters ^[^\x00]*[^\s\x00][^\x00]*$ Only the batch item with this caller-supplied ID. Unique within the batch. |
| status | string (BatchItemStatus) Enum: "pending" "settled" "failed" "voided" Lifecycle of one submitted Batch item. A linked Transfer's state is projected asynchronously onto its item. Pocket assigns |
| reference | string <= 255 characters ^[^\x00]*$ Exact, case-sensitive reference filter. References are not unique; matches are paginated. Whitespace is retained; an empty query value is treated as omitted. |
| cursor | string <= 8192 characters Opaque cursor ( |
| limit | integer [ 1 .. 100 ] Default: 25 |
{- "has_more": true,
- "next_cursor": "string",
- "data": [
- {
- "item_id": "line-001",
- "batch_id": "batch_01j5gr81vpavng9emxaepf2be3",
- "status": "pending",
- "transfer_id": "txn_01jhv705whdehqd34b4113r1q8",
- "destination": {
- "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej"
}, - "amount": "100.00",
- "currency": "USD",
- "reference": "string",
- "memo": "string",
- "failure_code": "string",
- "failure_message": "string",
- "metadata": {
- "property1": "string",
- "property2": "string"
}, - "created_at": "2026-07-31T09:00:00Z",
- "updated_at": "2026-07-31T09:00:00Z"
}
]
}Required scopes: batches:read and transfers:read.
Customer eligibility: authenticated business Customer only.
Only items that produced a transfer appear here. Every submitted item appears in
GET /v1/batches/{batch_id}/items. An item that fails before transfer creation, such as for
insufficient funds when it runs, has transfer_id: null. The API orders results by
(created_at ASC, id ASC).
A cursor binds Customer, Batch and status, not limit (default 25,
range 1–100). Repeat the status filter with the cursor; filtering precedes
pagination. Empty scalar query values are treated as omitted.
This list contains only linked, visible Transfer views from the pinned source Account. If a payment's outcome is unknown, check the Batch items and source Account activity before submitting it again.
| batch_id required | string^batch_[0-7][0-9abcdefghjkmnpqrstvwxyz]{25}$ |
| status | string (TransferStatus) Enum: "pending" "settled" "failed" "voided"
|
| cursor | string <= 8192 characters Opaque cursor ( |
| limit | integer [ 1 .. 100 ] Default: 25 |
{- "has_more": true,
- "next_cursor": "string",
- "data": [
- {
- "id": "txn_01k0d2f6h8j4m7n9p3q5r1s0vw",
- "method": "internal",
- "reference": "payrun-2026-07",
- "memo": "July salary",
- "source": {
- "account_id": "acc_01j8ma3wv3mepwabfxxs0yc395"
}, - "destination": {
- "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej"
}, - "batch_id": null,
- "amount": "100.00",
- "currency": "USD",
- "status": "pending",
- "transitioned_at": {
- "pending": "2026-07-31T11:00:00Z",
- "settled": null,
- "failed": null,
- "voided": null
}, - "created_at": "2026-07-31T11:00:00Z",
- "updated_at": "2026-07-31T11:00:00Z"
}
]
}Same /v1 paths and 23 public operations. No new resource families.
Request keys move from the body to the Idempotency-Key header. Recipient
removal gains optional saved-response protection.
| Operation | Header | Matching retry within 24 hours replays |
|---|---|---|
| Create internal Transfer | Required | Saved 201 body and Location. |
| Create Batch | Required | Saved 202 body and Location, not execution progress. |
| Create Recipient | Required | Saved original response: 201 for new, 200 for existing or restored. |
| Create Account | Required | Saved 201 body and Location. |
| Update Account (PATCH) | Optional | Saved 200 body. |
| Replace Recipient (PUT) | Optional | Saved 200 body. |
| Remove Recipient (DELETE) | Optional | Saved 204, without removing a re-added Recipient. |
Keys are scoped to the Customer and operation. Replays return the original response,
not current state; use GET for that. Changed input under an unexpired idempotency key returns 409
with outer aborted, details.code: idempotency_conflict and
details.param: Idempotency-Key, without existing_id. Replay does not extend
the 24-hour window. After expiry, reusing the key can create another resource
or payment.
Changing between destination.customer_id and destination.account_id while
reusing the same idempotency key within 24 hours returns 409 Conflict.
Authentication, authorization and request validation still apply to retries.
429 rate_limited without creating a Transfer.
Batch items report limit_exceeded.404 instead of 500. The account_id filter is optional.503 responses, which use
details.maintenance and details.retry_after_seconds instead of a Pocket
reason and request ID.Regenerate clients from this contract. Send request keys in the Idempotency-Key header,
remove the body attribute, and keep the same key for retries of one operation.
Retries return the original response. Use GET for current state and check the
payment outcome before resubmitting after 24 hours.
accounts:read and accounts:write scopes.source.customer_id, which selects its default Account, or an owned Account in
source.account_id. A Customer destination still uses destination.customer_id and
receives into its immutable default Account.rcp_…). Retrieve, replace and remove operations
now address the Recipient rather than its linked Customer.PATCH to PUT. The request supplies the
complete mutable state: label, reference, status and metadata.404 recipient_not_found and creates nothing.idempotency_key is supplied.
Other operations may accept an idempotency key, but V1 does not guarantee
replay or duplicate suppression for them.400, and the caller can restart from the first page.{ code, message, details } envelope. Recipient
operations document more specific error detail schemas and HTTP responses.
Retry-After is optional on rate-limit responses.413
responses.| 0.2.0 | 0.4.0 |
|---|---|
Account-scoped balances and activity using account_id |
Customer-scoped balance and activity using customer_id. Accounts are not included. |
| Internal, ACH, wire, onchain and IBFT Transfer creation | Internal Customer-to-Customer Transfer creation only. |
Recipients addressed through their linked Customer ID and updated with PATCH |
Stable rcp_… Recipient IDs and complete replacement with PUT. Responses no longer include contact or onboarding fields. |
| Instruments, funding rules, FX quotes, events and webhooks | These operations are not included. |
Flat { code, message, param, request_id } errors |
{ code, message, details } errors. |
| At least 24 hours of idempotent replay for every operation that accepted a key | 24 hours of replay for Recipient creation, Recipient replacement when a key is supplied, and Batch creation. Other operations do not guarantee replay or duplicate suppression. |
These are historical 0.4.0 contract notes, not the supported surface or retry rules for 0.6.0. Use the current operation definitions when integrating.
api.pocketmoney.host to
api.relay.pocketmoney.global.cus_…) with Customer-owned
Accounts (acc_…) as directly addressable funds containers. Removed the
public Customer hierarchy and subcustomer management operations.recipients_only Batch policy.epi_…) for bank accounts and
wallet addresses, including Customer-held and third-party bank holders.Regenerate typed clients from the 0.2.0 specification before switching endpoints. API keys remain associated with one Customer. Update integrations that used the 0.1.0 subcustomer model as follows:
| 0.1.0 integration | Required 0.2.0 change |
|---|---|
Create, list, retrieve or update subcustomers through /v1/customers |
Use /v1/accounts and /v1/accounts/{account_id}. Store the returned acc_… ID. Do not derive it from or substitute a previous cus_… ID. |
Address balances and money activity by customer_id |
Use account_id. This includes source.account_id, Account-relative balance, Transaction and Deposit fields, and the account_id or source_account_id list filters. A Customer destination for an internal Transfer still uses destination.customer_id. |
Read /v1/customers/{customer_id}/balances or filter balances by parent_customer_id |
For one Account, use /v1/accounts/{account_id}/balance. For a list, use /v1/accounts and /v1/balances. |
Manage funding rules below /v1/customers/{customer_id}/funding_rules with destination_customer_id |
Use /v1/accounts/{account_id}/funding_rules. Supply destination_account_id. Responses use source_account_id and destination_account_id. |
Use member endpoints, members:* scopes or recipient_policy=payable_members |
Use Recipient endpoints, recipients:* scopes and recipient_policy=recipients_only. |
Submit Batches with source.customer_id |
Keep the authenticated Customer to use its default Account, or submit a specific owned Account as source.account_id. Batch-item Customer destinations remain destination.customer_id. |
Persist 0.1.0 list cursors or handle member event resource references |
Restart pagination without the saved cursor. Handle recipient and new Account-related resource references, and continue accepting unknown event types and resource identifiers. |
Grant customers:read or customers:write |
Grant accounts:read or accounts:write. Grant the new instruments:* scopes only when using instrument operations. |
Do not mechanically replace every customer_id. Customer destinations,
instrument ownership paths and the top-level Event.customer_id continue to
use cus_… identifiers.