Pocket Money - Relay API (0.1.0-draft)

Download OpenAPI specification:

Draft for review. This is the Relay API v1 contract under review with customers and internal teams. It may change before release. An endpoint's presence here does not mean it is available today.

The Relay API moves money between accounts in Pocket and out to external banks and networks.

  • A transfer is money leaving an account.
  • A deposit is money arriving in an account.
  • A method names how the money traveled, such as internal, ach, wire, or ibft.

It also reads balances, instruments and account activity.

Money operations are scoped to an account, referenced by id: a customer (cus_…) or a business or sub-account (biz_…). The same money endpoints work for either type.

Pocket publishes this OpenAPI contract rather than generated client SDKs.

API surface

Resource Purpose
Accounts Create and manage sub-accounts, balances, and the member roster.
Transactions Read a unified money-movement activity feed.
Transfers Create, list, and retrieve outbound money movements.
Deposits List and retrieve inbound money movements.
FX quotes Price a cross-currency transfer before creating it.
Instruments Register external bank accounts used as withdrawal destinations.
Funding rules Route matching settled deposits from a parent account into a sub-account.
Events Read and replay event notifications, and configure webhook delivery.

Transfers and deposits

A transfer moves money out of an account. A deposit is money coming in.

An accepted internal (Pocket-to-Pocket) move creates one txn_ ID:

  • For the source account, it is a transfer.
  • For the destination account, it is a deposit.
  • Both account-relative views exist immediately with status set to pending.
  • Each view remains visible to callers authorized to read its account.

Transactions form the unified activity feed. When a transfer or deposit object exists, the feed points to it. A pending movement has not posted to either balance:

  • At acceptance, the source amount moves from available to reserved.
  • The source settled amount remains unchanged.
  • Every destination balance amount remains unchanged.
  • At settlement, both views become settled together.
  • The source reservation posts out, and the destination settled and available amounts are credited.

Clients create transfers, not deposits. Pocket creates deposits for incoming Automated Clearing House (ACH) payments, the destination side of internal transfers and Pocket-funded advances or rewards.

Card refunds appear in the activity feed as deposits, but do not have deposit objects.

Authorization is based on the account hierarchy associated with the API key. A key may act only on its associated account and that account's complete owned sub-account hierarchy.

Authentication

Each bearer API key is associated with one account:

Authorization: Bearer <api_key>
  • Every request requires the key. A missing or invalid key returns 401 unauthorized.
  • Within its capability scopes, the key applies to its associated account and that account's complete owned sub-account hierarchy.
  • There are no per-account grants, delegated accounts, or key-specific subtrees.

An account outside that hierarchy, or a resource belonging to it, returns 404 as if it did not exist. 403 is reserved for actions the key may not perform on an account in the hierarchy.

Scopes

API keys also carry capability scopes. Each operation states its required scopes at the start of its description and in x-required-scopes for tooling. The key needs every listed scope.

Scopes control what a key may do. Its account hierarchy controls which accounts it may act on.

  • A scope never grants access outside the associated account hierarchy.
  • A missing scope returns 403 for a resource in that hierarchy.
  • An account outside the hierarchy returns 404.

Broad scopes satisfy granular requirements:

  • read satisfies any *:read requirement.
  • write satisfies any *:write or *:read requirement.
  • A granular *:write scope includes the corresponding *:read operations and the reads needed to perform and inspect that write. For example, transfers:write permits the source balance details returned while creating a transfer.
  • A new high-risk capability may require an explicit granular scope even when a key has write.

Available scopes:

Scope Permits
read All current read operations.
write All current read and write operations.
accounts:read / accounts:write Account records and metadata.
balances:read Account balances.
transactions:read Unified money-movement activity.
transfers:read / transfers:write Outbound money movement.
deposits:read Inbound money movement.
batches:read / batches:write Batch transfer jobs and line items.
instruments:read / instruments:write External payment instruments.
members:read / members:write Member rosters.
funding_rules:read / funding_rules:write Funding rules and previews.
webhooks:read / webhooks:write Webhook endpoint configuration and secrets.
events:read / events:write Event log reads and event replay.

Authorization and privacy

Transfers are authorized from the source:

  • source.account_id must be the account associated with the API key or one of its sub-accounts.
  • The destination only needs to be valid for the method.
  • transfers:write applies across every eligible account in that hierarchy, subject to account state and method-specific requirements.

Sending money to an account does not grant read access or any other access to it.

A business may keep a roster of customers it pays. A member is a reference to a Pocket customer account, not delegated access to that account.

  • Changing or removing the roster reference never changes the customer account.
  • Rostered customer accounts do not appear in the business's account, balance, transaction, or instrument lists.
  • Direct reads return 404 unless the account is in the hierarchy associated with the API key.

An external payment instrument (epi_…) is a verified outside destination for money leaving Pocket, such as a bank account.

  • It is not a Pocket account.
  • It does not hold a Pocket balance.
  • It is not used for Pocket-to-Pocket transfers.
  • It is private to its owning account.

A business can create, list, remove, and use instruments only for accounts in the hierarchy associated with the API key. A roster relationship does not change that rule.

Money-movement field visibility:

  • memo is the payment note and may be visible to both sides.
  • reference and metadata are visible only from the account-owner side on which they were set.

A counterparty does not see your reference or metadata. A key that owns both sides can read both account-relative views.

Accounts

An account is a money-holding party identified by a prefixed id (biz_… or cus_…).

  • All accounts are customer-owned.
  • Whether Pocket or the API provisioned an account has no public significance.
  • A customer's portfolio is its principal account plus its complete owned sub-account hierarchy, recursively.
  • Money operations reference accounts by id.

The optional reference is an opaque, non-unique external reference. Use the reference filter on list accounts or list balances to find matches; a filter may return more than one.

A wrong-prefix ID for an operation returns 400.

Amounts

Amounts are decimal strings in major units of the currency. For example, "100.00" means 100 USD. USD supports at most two decimal places.

  • Amounts are always JSON strings, never JSON numbers.
  • Write amounts must be greater than zero.
  • A USD write uses an unsigned decimal with 1–16 integer digits and at most two fractional digits.
  • Commas, whitespace, scientific notation, and unnecessary leading zeros are not accepted.
  • The maximum USD write amount is "9999999999999999.99".
  • Valid examples include "1", "1.0", "1.00", "0.01", and "2500.50".
  • Decimal-equivalent forms such as "1", "1.0", and "1.00" produce the same idempotency request fingerprint.

Pocket rejects an amount outside this boundary with 400 invalid_request; param identifies the actual request field, such as amount, source_amount, or items[n].amount. No resource is created or changed, and the idempotency_key is not bound.

FX pricing

A USD-to-PKR Inter Bank Funds Transfer (IBFT) can use an explicit quote or accept the current price as part of transfer creation.

  • A quote is source-fixed in v1: the caller supplies the gross USD source amount.
  • The destination currency is supplied explicitly and must match the destination instrument.
  • Creating a quote does not reserve the source account's USD or change any customer balance.
  • Each fee is already rounded in its currency.
  • net_source_amount equals source_amount minus the sum of source-currency fees.
  • The gross source amount must exceed the total source-currency fees. Otherwise Pocket returns 400 invalid_request, with param identifying the submitted source-amount field.
  • rate is destination-currency units per one source-currency unit.
  • destination_amount equals net_source_amount multiplied by rate, rounded half-even to the destination currency's precision. PKR supports two decimal places.
  • On an accepted IBFT Transfer, amount equals fx.source_amount, and currency equals fx.source_currency (USD in v1).
  • fx.destination_amount is the PKR amount delivered to the destination instrument.
  • FX fees are included within the gross source amount. They are not added on top of the Transfer's amount.

Display and reconcile the amounts, fees and rate returned on the quote and transfer. Do not recompute them.

Request body limits

JSON request-body limits:

  • Most operations: 1 MiB (1,048,576 bytes).
  • POST /v1/batches: 10 MiB (10,485,760 bytes).

The limit applies to the UTF-8-encoded JSON body, including JSON syntax and whitespace but excluding HTTP headers. Pocket enforces it even when Content-Length is absent.

An oversized body returns 413 Payload Too Large with code payload_too_large. The error has no field-specific param.

An oversized request creates or changes nothing and does not bind its idempotency key. OpenAPI 3.0 cannot express these whole-body limits. A request can satisfy every field limit and still exceed the body limit.

Dates

All timestamps are strings in RFC 3339 / ISO 8601 format.

  • Responses use UTC with a Z suffix, e.g. 2026-07-31T09:00:00Z.
  • Requests accept any valid instant, including numeric offsets. For example, 2026-07-31T14:00:00+05:00 and 2026-07-31T09:00:00Z are the same instant.

Idempotency

A caller-supplied idempotency_key is required for:

  • Resource-creating POST requests for accounts, transfers, FX quotes, batches, instruments, funding rules, and webhook endpoints.
  • Webhook secret rotation.

PATCH requests accept a key optionally. Without one, retrying after a lost response may apply an update more than once.

Use a non-blank random value of 1–255 characters, such as a universally unique identifier (UUID). Mint it once per logical operation and reuse it only when retrying that operation.

All operations that accept an idempotency key share one namespace per associated account and environment.

The namespace spans:

  • Resource families and endpoints.
  • Transfer methods.
  • Every account in the associated account hierarchy.
  • Optional idempotent PATCH requests.
  • Webhook secret rotation.

The namespace is not scoped by source account, endpoint, resource family or API credential. Keys associated with the same account share it, including after credential rotation. Sandbox and production use separate namespaces.

Each key identifies one logical operation during the retention window. Reusing it elsewhere in the same namespace with a different request fingerprint returns 409.

The fingerprint covers:

  • The HTTP method.
  • The canonical path.
  • A canonical hash of the request body.

Metadata key order does not affect the hash. Amounts are compared as decimals, so "10.4" and "10.40" are equivalent.

A key is bound by the first request that succeeds with a 2xx response. The binding lasts at least 24 hours.

  • An identical replay returns the original status code and the object's current representation. Fields may have changed since creation.
  • The same key with a different fingerprint returns 409.
  • A duplicate received while the original request is still running returns 409 until the original completes.

A failed request that creates or updates nothing leaves an unbound key available. This includes resource-state 409 responses. Retry the unchanged operation with the same key.

Quote creation and transfer creation are separate logical operations and use separate keys. For an IBFT transfer request:

  • If a Transfer is created, its key is bound. An identical replay returns that Transfer even though its quote is now accepted.
  • If no Transfer is created, its key remains unbound and the unchanged request may reuse it.
  • Replacing an expired, canceled, or already accepted quote changes the financial instruction. Submit the changed request with a new key.

A 409 means one of the following:

  • The key is bound to a different request fingerprint.
  • The original request is still in flight.
  • The resource is in a conflicting state.

When retrying after an error:

  • After 409 idempotency_conflict, never change the request while reusing the key. Retry an unchanged in-flight duplicate, or retrieve existing_id when one is returned.
  • After a resource-state 409 that created no object, the key remains unbound. Retry the unchanged request with the same key. Mint a new key if the financial instruction changes.
  • After a 5xx response or timeout, keep the same key until you receive a definitive response.

After the retention window expires, a key may be treated as new and can create a second object.

idempotency_key is not returned on objects and is not a business identifier. To store a pay-run, invoice, employee, or other external ID, use the filterable reference field.

One-time values are never replayed. A webhook secret appears only in the original successful create or rotate response.

If that response is lost, the same idempotency_key will not recover the secret. Rotate with a new key to get a fresh one.

Pagination

Lists return { data, has_more, next_cursor }. Ordering varies by endpoint. A valid next_cursor continues the first page's ordering.

  • Pass next_cursor back as cursor, together with limit.
  • Treat the cursor as opaque; do not parse it.
  • Re-supply the same filters on every page.
  • A cursor/filter mismatch returns 400.
  • An expired or invalid cursor returns 400.

After a cursor error, restart from the first page.

Filtering

Query filters accept one value unless stated otherwise. The money-movement method filter accepts a comma-separated list, for example method=ach,wire. Each endpoint accepts only the methods it can return. An unsupported method returns 400 with code invalid_request and param=method.

Rate limits

Any endpoint may return 429 with code rate_limited and a Retry-After header when you exceed the rate limit. Its value is a non-negative base-10 integer giving the number of seconds to wait after receiving the response. Pocket does not use the HTTP-date form.

For example, Retry-After: 30 means wait at least 30 seconds before retrying.

Errors

Error responses use one JSON shape:

{
  "code": "invalid_request",
  "message": "destination.account_id has an unexpected prefix",
  "param": "destination.account_id",
  "request_id": "req_01jj0m9w2k3n5p7q9r1s3t5v7w"
}
  • code is the stable, machine-readable reason. Preserve unknown codes and fall back to the HTTP status.
  • message explains the error to a human. Do not branch on its wording.
  • param identifies the request field involved, when applicable; otherwise it is absent or null.
  • request_id identifies the request. Include it when contacting Pocket support.

Common HTTP statuses:

Status Meaning
400 Malformed input or failed validation.
401 Missing or invalid authentication.
402 The source has insufficient available funds.
403 The key lacks a required scope or cannot act on a visible resource.
404 The resource does not exist or is outside the account hierarchy associated with the API key.
409 The request conflicts with current resource or idempotency state.
413 The UTF-8 JSON request body exceeds the endpoint's limit.
429 A rate limit was exceeded. Wait for the seconds specified by Retry-After.
500 Pocket encountered an internal error.
503 Pricing or another temporary dependency is unavailable. Follow Retry-After when present.

Reads can be retried after transient server failures. After a timeout or 500 on a money-writing request, retry only with the same idempotency_key; see Idempotency above.

Webhook signatures

When webhook delivery is enabled, every request carries a Pocket-Signature header. Use it to verify that the request came from Pocket and was not altered in transit.

Verify the signature before acting on the payload.

Treat the event as a hint, not the source of truth. Fetch the resource named by subject to read its current state. Retrieving the event returns its immutable notification envelope, not the current subject state.

The header is a comma-separated list of fields:

Pocket-Signature: t=1690794000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
  • t: the Unix timestamp (seconds) when we signed the request.
  • v1: a hex-encoded HMAC using the SHA-256 hash function. There may be more than one v1 value (space- or comma-separated) during secret rotation; a request is valid if any matches.

The signing secret is returned when you create a webhook endpoint or call rotate_secret. It is shown only in those responses, so store it.

To verify a request:

  1. Read the raw request body as bytes. Do not parse and re-serialize it first: any reformatting changes the bytes and breaks the signature.
  2. Extract t and the v1 value(s) from the Pocket-Signature header.
  3. Build the signed payload by concatenating t, a literal ., and the raw body: signed_payload = t + "." + raw_body.
  4. Compute HMAC-SHA256(signed_payload, secret) and hex-encode it.
  5. Constant-time compare your result against each v1. If none match, reject the request.
  6. Reject if t is outside your tolerance for the current time (we recommend 5 minutes) to guard against replay.

During rotate_secret, Pocket signs with both secrets for a grace period. Verify against both until you have switched to the new secret.

Set up an employee for payroll

1. Add the employee to the member roster

Supply the identifiers you know. This example uses email and phone:

POST /v1/accounts/{biz_account_id}/members
{
  "email": "employee@example.com",
  "phone": "+12025550123",
  "metadata": {
    "employee_id": "EMP-1042"
  }
}

The response returns the roster relationship and its customer_id.

  • A newly created roster relationship returns 201 Created and its relative URL in the Location header.
  • An existing or restored roster relationship returns 200 OK.
  • An existing Pocket customer is linked immediately.
  • If no customer matches, an email or phone can create a customer whose member status is onboarding_required.

2. Wait until the employee can be paid

Retrieve the member using the returned customer_id:

GET /v1/accounts/{biz_account_id}/members/{customer_id}

Include the employee in payroll when capabilities.can_receive_transfers is true.

Run a payroll batch

1. Submit the payroll transfers

Give each employee a unique item_id for this batch. Use the employee's customer_id as the destination account ID.

POST /v1/batches
{
  "idempotency_key": "a15e4ae4-52f3-460c-9c1c-5f6b7b90cc44",
  "source": { "account_id": "biz_..." },
  "recipient_policy": "payable_members",
  "reference": "payroll-2026-07",
  "items": [
    {
      "item_id": "line-001",
      "destination": { "account_id": "cus_..." },
      "amount": "100.00",
      "currency": "USD",
      "reference": "EMP-1042"
    },
    {
      "item_id": "line-002",
      "destination": { "account_id": "cus_..." },
      "amount": "120.00",
      "currency": "USD",
      "reference": "EMP-1089"
    }
  ]
}

The 202 response returns the batch and its batch_... ID.

2. Track the batch

GET /v1/batches/{batch_id}

When every transfer settles, status becomes completed and item_counts.settled equals item_counts.total.

3. Reconcile each employee

GET /v1/batches/{batch_id}/items

Match each result to the submitted line by item_id. Its transfer_id is the resulting txn_... ID.

Send money to a Pocket account

Send money to a Pocket customer or another Pocket account:

POST /v1/transfers/internal
{
  "idempotency_key": "d9f80740-7f8e-4f16-b5cc-4fd5bf7d3b21",
  "source": { "account_id": "biz_..." },
  "destination": { "account_id": "cus_..." },
  "amount": "100.00",
  "currency": "USD",
  "reference": "payrun-2026-07"
}

Read balances and activity

Read the business's balances and activity. Use parent_id to limit balances to the direct sub-accounts of one business account.

GET /v1/balances
GET /v1/balances?parent_id=biz_...
GET /v1/transactions?account_id=biz_...

Route incoming deposits to a child account

1. Create the child account

POST /v1/accounts
{
  "idempotency_key": "7a4409dc-5612-41d9-8e5d-e2fbfb657f83",
  "parent_id": "biz_...",
  "name": "Payroll operations",
  "reference": "payroll-operations"
}

The returned child account is active and can be used as a funding-rule destination.

2. Preview the matching criteria

POST /v1/accounts/{parent_account_id}/funding_rules/preview
{
  "criteria": [
    { "field": "sender_name", "operator": "contains", "value": "ACME PAYROLL" }
  ]
}

Use the preview results to confirm the criteria match the intended settled deposits.

3. Create the funding rule

POST /v1/accounts/{parent_account_id}/funding_rules
{
  "idempotency_key": "c20d05c2-40c6-48f6-b2b7-a1aaac33370c",
  "destination_account_id": "biz_...",
  "criteria": [
    { "field": "sender_name", "operator": "contains", "value": "ACME PAYROLL" }
  ]
}

The rule begins enabled. When a matching deposit into the parent account settles, its full amount moves to the child account as an internal transfer.

4. View transfers created by the rule

GET /v1/transfers?funding_rule_id={funding_rule_id}

Withdraw funds

Withdraw from a business-owned balance to that account's own external instrument.

1. Register the external bank instrument

POST /v1/accounts/{biz_account_id}/instruments
{
  "idempotency_key": "b57ba9a4-0f17-4c5d-99cd-e1467069270e",
  "type": "bank_account",
  "currency": "USD",
  "bank_account": {
    "holder_name": "Example Business LLC",
    "account_number": "000123456789",
    "routing_number": "021000021",
    "country": "US"
  }
}

2. Check the review status

If a newly registered instrument is in_review, poll it while that status remains:

GET /v1/instruments/{instrument_id}

Proceed only when the instrument is approved. declined is terminal; correct the bank details and register a new instrument.

3. Create the withdrawal

Use the method-specific endpoint. For ACH:

POST /v1/transfers/ach
{
  "idempotency_key": "15dfe9e2-04e2-4e9a-b448-2d71c46c2219",
  "source": { "account_id": "biz_..." },
  "destination": { "instrument_id": "epi_..." },
  "amount": "500.00",
  "currency": "USD",
  "memo": "PAYROLL"
}

Use POST /v1/transfers/wire instead for a wire withdrawal. ACH and wire have different memo limits; see the endpoint request schemas.

To withdraw USD converted to PKR, see Withdraw USD as PKR.

Withdraw USD as PKR

Convert USD and withdraw the resolved PKR amount through Pocket's instant IBFT route to the source account's own approved Pakistani bank instrument. V1 does not expose the standard PKR cash-out route and does not promise a delivery time.

1. Find a supported bank

GET /v1/banks?country=PK&method=ibft

Use the returned code when registering the bank account.

2. Register the PKR instrument

POST /v1/accounts/{biz_account_id}/instruments
{
  "idempotency_key": "0b57ba9a-4f17-4c5d-99cd-e14670692710",
  "type": "bank_account",
  "currency": "PKR",
  "bank_account": {
    "country": "PK",
    "bank_code": "hbl",
    "account_number": "PK16HABB1234567890123456"
  }
}

account_number may be a supported local account number or Pakistani International Bank Account Number (IBAN). If the instrument is in_review, poll it while that status remains. Proceed only when it is approved. If it is declined, correct the bank details and register a new instrument.

3. Request a quote

POST /v1/fx_quotes
{
  "idempotency_key": "9f1c2a34-6a3d-4e0b-9c7e-2f8b1d5a7e21",
  "source": { "account_id": "biz_..." },
  "destination": { "instrument_id": "epi_..." },
  "method": "ibft",
  "source_amount": "1000.00",
  "source_currency": "USD",
  "destination_currency": "PKR"
}

The response fixes the rate, fees and PKR amount until expires_at, while the quote remains open. The quote cannot be accepted at or after expires_at. Creating it does not reserve USD.

4. Create the transfer

POST /v1/transfers/ibft
{
  "idempotency_key": "b2e6d1f0-3c44-4d55-8f66-77a889900abc",
  "source": { "account_id": "biz_..." },
  "destination": { "instrument_id": "epi_..." },
  "amount": "1000.00",
  "currency": "USD",
  "destination_currency": "PKR",
  "pricing": {
    "mode": "quote",
    "quote_id": "fxq_..."
  },
  "reference": "cashout-1042"
}

To skip the separate quote request, use pricing.mode=current. Pocket then creates and accepts an equivalent quote as part of transfer creation. If the request fails, neither the quote nor the transfer is created. Add minimum_destination_amount when the transfer must not proceed below a particular PKR amount.

5. Track the withdrawal

Poll GET /v1/transfers/{transfer_id} until the transfer is terminal. amount is the gross USD debit; fx contains the fee, rate and PKR amount to reconcile. A failed transfer releases the full USD reservation and is not automatically retried.

Transactions

Money-movement activity across the account hierarchy associated with the API key. It includes transfers, deposits and the transaction entry for each account-relative view. Pending entries are visible before they post to a balance.

Use the transaction ID to fetch the transfer or deposit object when one exists.

List transactions

Required scope: transactions:read.

List money-movement activity across the account hierarchy associated with the API key. Rostered customer activity is excluded unless the customer account independently belongs to that hierarchy.

Narrow results with account_id, kind, method, status, amount, reference, created date, or settled date.

When account_id is supplied, it selects one owned account's viewpoint.

Each item is a transaction (txn_…) seen from one account's side:

  • A feed entry is identified by (id, account_id).
  • The same txn_… can appear twice when both sides of an internal move are in the account hierarchy associated with the API key.
  • Use kind and id to fetch a transfer or deposit object when one exists.

An accepted internal transfer creates both authorized account-relative views at once:

  • The source entry is kind=transfer and begins pending.
  • The destination entry is kind=deposit and begins pending.
  • Both become settled, failed, or voided together.

A rejected request creates no feed entry, transfer or deposit.

details carries method-specific facts and may gain fields before settlement. Events are references, not snapshots; fetch the transaction for its current details.

If you sent money to a customer account, your feed shows your transfer and its status, not the customer's resulting balance or unrelated activity. Verification microdeposits, declined card authorizations, and Pocket-internal treasury operations are excluded.

The v1 feed does not model post-settlement returns or reversals. A future return will be a new balance-moving entry, not a change to an already settled status.

Authorizations:
bearerAuth
query Parameters
account_id
string
Example: account_id=biz_01j8ma3wv3mepwabfxxs0yc395

Select the viewpoint of the account associated with the API key or one of its sub-accounts. Roster membership is not sufficient.

  • Malformed or wrong-prefix value: 400; param is account_id.
  • Well-formed non-owned account: 404.
  • Owned account with no list matches: empty page.
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" "ibft" "swift" "faster_payments" "payment_link" "card"
Example: method=ach,wire

Filter transactions by method. Comma-separate to match more than one (e.g. method=ach,wire).

status
string (TransactionStatus)
Enum: "pending" "settled" "failed" "voided"

pending (accepted, not yet settled), settled (posted to the balance), failed, or voided. voided is platform-assigned and is not caller cancellation.

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. 100.00.

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. 100.00.

reference
string

Exact match on your opaque reference for the object. Not unique; may match more than one object, and results are paginated.

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). Pending, failed, and voided items have no settlement time and are excluded when this filter is used.

settled_before
string <date-time>
Example: settled_before=2026-07-31T09:00:00Z

Only items settled at/before this time (inclusive). Pending, failed, and voided items have no settlement time and are excluded when this filter is used.

cursor
string

Opaque cursor (next_cursor from a prior page). Re-supply the same filters; a mismatch, expired cursor, or invalid cursor returns 400.

limit
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "has_more": true,
  • "next_cursor": "string",
  • "data": [
    ]
}

Retrieve a transaction

Required scope: transactions:read.

Fetch one transaction entry by ID, e.g. /v1/transactions/txn_….

A transaction entry is one account's viewpoint on a money movement.

  • If exactly one side is in the account hierarchy associated with the API key, account_id may be omitted.
  • If several sides are in that hierarchy, supply account_id; omitting it returns 400; param is account_id.
  • A malformed or wrong-prefix account_id returns 400; param is account_id.
  • A well-formed account the customer does not own returns 404.
Authorizations:
bearerAuth
path Parameters
transaction_id
required
string^txn_
Example: txn_01jjftqbz0n1vzey0pqrq3qmnn
query Parameters
account_id
string
Example: account_id=biz_01j8ma3wv3mepwabfxxs0yc395

Select one owned account's viewpoint of this transaction.

  • If exactly one side is in the account hierarchy associated with the API key, omit this parameter or supply that account ID.
  • If several sides are in that hierarchy, account_id is required. Without it, the request returns 400 with param=account_id.
  • A malformed or wrong-prefix value returns 400; param is account_id.
  • A well-formed non-owned account returns 404.

Responses

Response samples

Content type
application/json
{
  • "id": "txn_01jjftqbz0n1vzey0pqrq3qmnn",
  • "account_id": "biz_01j8ma3wv3mepwabfxxs0yc395",
  • "kind": "deposit",
  • "method": "ach",
  • "source": null,
  • "destination": {
    },
  • "amount": "2500.00",
  • "currency": "USD",
  • "status": "settled",
  • "fx": null,
  • "metadata": { },
  • "details": {
    },
  • "created_at": "2026-07-30T14:10:00Z",
  • "settled_at": "2026-07-31T09:00:00Z"
}

Transfers

Move money out of the account associated with the API key or one of its sub-accounts.

An internal transfer pays another Pocket account. ach and wire withdraw USD to the source account's own approved external instrument. ibft converts USD and withdraws PKR through Pocket's instant route to the source account's own approved Pakistani bank instrument. V1 does not expose the standard PKR cash-out route or promise a delivery time. Each method has its own write endpoint.

List transfers

Required scope: transfers:read.

Outbound money movements across the account associated with the API key and its complete owned sub-account hierarchy.

  • Every method that produces a transfer object is included.
  • Card activity is feed-only; see Transactions.

Narrow results with method, source_id, destination_id, status, amount, funding_rule_id, reference, created date, or settled date.

source_id restricts the list to one owned source account.

destination_id is a counterparty filter. It may identify a non-owned Pocket account or an external instrument already visible on one of the customer's transfers.

Authorizations:
bearerAuth
query Parameters
method
Array of strings (TransferMethod)
Items Enum: "internal" "ach" "wire" "ibft"
Example: method=ach,wire

Filter transfers by method. Comma-separate to match more than one (e.g. method=ach,wire). A method that cannot produce a Transfer returns 400 invalid_request with param=method rather than an empty result.

source_id
string

Filter by an owned source account ID (biz_…/cus_…). A malformed or wrong-prefix value returns 400; param is source_id. A well-formed non-owned account returns 404; an owned account with no matches returns an empty page.

destination_id
string

Filter by a Pocket account for internal transfers or an external instrument for withdrawals. The destination may be non-owned.

  • Malformed or wrong-prefix value: 400; param is destination_id.
  • No matching visible transfers: empty page, without disclosing whether it exists.
status
string (TransferStatus)
Enum: "pending" "settled" "failed" "voided"

pending (accepted, not yet settled), settled (posted to the balance), failed, or voided. voided is assigned by Pocket for an accepted transfer that will not settle; there is no public cancel or void endpoint.

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. 100.00.

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. 100.00.

funding_rule_id
string

Only transfers or deposits initiated by this funding rule (frule_…).

reference
string

Exact match on your opaque reference for the object. Not unique; may match more than one object, and results are paginated.

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). Pending, failed, and voided items have no settlement time and are excluded when this filter is used.

settled_before
string <date-time>
Example: settled_before=2026-07-31T09:00:00Z

Only items settled at/before this time (inclusive). Pending, failed, and voided items have no settlement time and are excluded when this filter is used.

cursor
string

Opaque cursor (next_cursor from a prior page). Re-supply the same filters; a mismatch, expired cursor, or invalid cursor returns 400.

limit
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "has_more": true,
  • "next_cursor": "string",
  • "data": [
    ]
}

Create an internal transfer

Required scope: transfers:write.

Move funds from one Pocket account to another. source.account_id must be the account associated with the API key or one of its sub-accounts; destination.account_id must be a valid account that can receive funds. Sending money to a destination account does not grant read or other access to it.

At acceptance

  • Source: kind is transfer, method is internal, and status is pending.
  • Destination: kind is deposit, method is internal, and status is pending.
  • Both views are immediately visible to callers authorized for the corresponding account.
  • Source available decreases and reserved increases.
  • Source settled and every destination balance amount remain unchanged.

At settlement

Both views become settled. The source reservation posts out, and the destination settled and available amounts are credited.

On failure or voiding

Both views remain visible with the terminal status. The source reservation is released, and the destination is never credited.

A request rejected before a txn_… is created produces no transfer, deposit, or transaction resource.

Authorizations:
bearerAuth
Request Body schema: application/json
required
idempotency_key
required
string [ 1 .. 255 ] characters .*\S.*

Use a non-blank random value minted once for this create request so retries do not create duplicate transfers.

required
object (TransferSource)

Where a transfer pulls money from. This must be the account associated with the API key or one of its sub-accounts. In v1 this is a Pocket account. Supply exactly one key.

required
object (AccountReference)

A Pocket account ref (cus_…/biz_…). Referencing an account as a transfer destination does not grant read or admin access to it.

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, "100.00" means 100 USD.

  • The value must be greater than zero.
  • USD supports 1–16 integer digits and at most two fractional digits.
  • The maximum value is "9999999999999999.99".
  • Signs, commas, whitespace, scientific notation, leading-dot forms, trailing-dot forms, and unnecessary leading zeros are invalid.
  • The complete value may contain at most 19 characters.
  • Decimal-equivalent forms such as "1", "1.0", and "1.00" produce the same idempotency request fingerprint.

An invalid, non-positive or overflowing amount returns 400 invalid_request. param names the request field, such as amount, source_amount or items[n].amount. Pocket creates or changes nothing and does not bind the idempotency key. Other account and rail limits may impose a lower maximum.

currency
required
string (Currency)
Value: "USD"

Currency debited from or credited to the Pocket account.

  • USD is the complete supported set in v1.
  • Adding a value requires a coordinated specification and client update.
  • Cross-currency payouts may define separate destination-currency and foreign exchange (FX) fields.
reference
string <= 255 characters

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

Payment note carried on the movement and visible to the destination account when the method supports a note. Internal transfer memos can be up to 100 characters; longer values are rejected with 400 (param is memo) and are never truncated.

object (Metadata) <= 50 properties

Up to 50 caller-defined string key/value pairs. An empty object is valid.

Keys

  • 1–64 Unicode characters.
  • Compared exactly as supplied.
  • Not trimmed, case-folded, or Unicode-normalized.
  • No additional character-set restriction.

OpenAPI 3.0 cannot express the key-length limit for arbitrary properties, so Pocket enforces it at runtime. An invalid key returns 400 invalid_request with param=metadata. The key is not echoed.

Values

  • String values only.
  • Empty values are allowed.
  • Maximum 256 Unicode characters.
  • Preserved exactly as supplied, without trimming or Unicode normalization.

An overlong value returns 400 invalid_request. param is metadata.<key> when the key is valid, or metadata when the key is invalid. The value is never echoed.

Privacy

Metadata is visible only from the account-owner side on which it was set and is not shared with money-movement counterparties. Use it for opaque business identifiers and non-sensitive labels only.

Do not include:

  • Government identity values.
  • Bank-account, routing, or payment-card details.
  • Passwords, secrets, access tokens, or credentials.
  • KYC documents.
  • Biometric or health information.
  • Names, email addresses, or phone numbers when a dedicated field exists.

Pocket may reject metadata that appears sensitive, but the caller remains responsible for the data it supplies.

Responses

Request samples

Content type
application/json
{
  • "idempotency_key": "d9f80740-7f8e-4f16-b5cc-4fd5bf7d3b21",
  • "source": {
    },
  • "destination": {
    },
  • "amount": "100.00",
  • "currency": "USD",
  • "reference": "payrun-2026-07",
  • "memo": "July salary",
  • "metadata": {
    }
}

Response samples

Content type
application/json
{
  • "id": "txn_01jhv705whdehqd34b4113r1q8",
  • "method": "ach",
  • "reference": "payrun-2026-07",
  • "memo": "July salary",
  • "source": {
    },
  • "destination": {
    },
  • "batch_id": "string",
  • "funding_rule_id": "string",
  • "amount": "100.00",
  • "currency": "USD",
  • "status": "pending",
  • "failure_code": "string",
  • "failure_message": "string",
  • "fx": {
    },
  • "details": {
    },
  • "metadata": {
    },
  • "transitioned_at": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

Create an ACH transfer (withdrawal)

Required scope: transfers:write.

Withdraw funds to an approved external bank instrument over ACH. This is self-withdrawal only.

  • The destination must be the source account's own approved epi_….
  • A business cannot use a rostered customer's instrument.

The note (ACH company entry description) rides in memo. A destination instrument that is not approved returns 403 with code instrument_not_approved.

Authorizations:
bearerAuth
Request Body schema: application/json
required
idempotency_key
required
string [ 1 .. 255 ] characters .*\S.*

Use a non-blank random value minted once for this create request so retries do not create duplicate transfers.

required
object (TransferSource)

Where a transfer pulls money from. This must be the account associated with the API key or one of its sub-accounts. In v1 this is a Pocket account. Supply exactly one key.

required
object (InstrumentReference)

An external instrument ref (epi_…). For withdrawals, the instrument must belong to a source account in the hierarchy associated with the API key.

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, "100.00" means 100 USD.

  • The value must be greater than zero.
  • USD supports 1–16 integer digits and at most two fractional digits.
  • The maximum value is "9999999999999999.99".
  • Signs, commas, whitespace, scientific notation, leading-dot forms, trailing-dot forms, and unnecessary leading zeros are invalid.
  • The complete value may contain at most 19 characters.
  • Decimal-equivalent forms such as "1", "1.0", and "1.00" produce the same idempotency request fingerprint.

An invalid, non-positive or overflowing amount returns 400 invalid_request. param names the request field, such as amount, source_amount or items[n].amount. Pocket creates or changes nothing and does not bind the idempotency key. Other account and rail limits may impose a lower maximum.

currency
required
string (Currency)
Value: "USD"

Currency debited from or credited to the Pocket account.

  • USD is the complete supported set in v1.
  • Adding a value requires a coordinated specification and client update.
  • Cross-currency payouts may define separate destination-currency and foreign exchange (FX) fields.
reference
string <= 255 characters

Your private external reference for this transfer. Optional, not unique; filter by it. Counterparties do not see it.

memo
string <= 10 characters

ACH company entry description. Must be 10 characters or fewer; longer values are rejected with 400 (param is memo) and are never truncated.

object (Metadata) <= 50 properties

Up to 50 caller-defined string key/value pairs. An empty object is valid.

Keys

  • 1–64 Unicode characters.
  • Compared exactly as supplied.
  • Not trimmed, case-folded, or Unicode-normalized.
  • No additional character-set restriction.

OpenAPI 3.0 cannot express the key-length limit for arbitrary properties, so Pocket enforces it at runtime. An invalid key returns 400 invalid_request with param=metadata. The key is not echoed.

Values

  • String values only.
  • Empty values are allowed.
  • Maximum 256 Unicode characters.
  • Preserved exactly as supplied, without trimming or Unicode normalization.

An overlong value returns 400 invalid_request. param is metadata.<key> when the key is valid, or metadata when the key is invalid. The value is never echoed.

Privacy

Metadata is visible only from the account-owner side on which it was set and is not shared with money-movement counterparties. Use it for opaque business identifiers and non-sensitive labels only.

Do not include:

  • Government identity values.
  • Bank-account, routing, or payment-card details.
  • Passwords, secrets, access tokens, or credentials.
  • KYC documents.
  • Biometric or health information.
  • Names, email addresses, or phone numbers when a dedicated field exists.

Pocket may reject metadata that appears sensitive, but the caller remains responsible for the data it supplies.

Responses

Request samples

Content type
application/json
{
  • "idempotency_key": "15dfe9e2-04e2-4e9a-b448-2d71c46c2219",
  • "source": {
    },
  • "destination": {
    },
  • "amount": "500.00",
  • "currency": "USD",
  • "memo": "PAYROLL"
}

Response samples

Content type
application/json
{
  • "id": "txn_01jhv705whdehqd34b4113r1q8",
  • "method": "ach",
  • "reference": "payrun-2026-07",
  • "memo": "July salary",
  • "source": {
    },
  • "destination": {
    },
  • "batch_id": "string",
  • "funding_rule_id": "string",
  • "amount": "100.00",
  • "currency": "USD",
  • "status": "pending",
  • "failure_code": "string",
  • "failure_message": "string",
  • "fx": {
    },
  • "details": {
    },
  • "metadata": {
    },
  • "transitioned_at": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

Create a wire transfer (withdrawal)

Required scope: transfers:write.

Withdraw funds to an approved external bank instrument over wire: self-withdrawal only. The { instrument_id } destination must be the source account's own approved epi_…; customer-owned instruments are not usable by a business.

The note (Originator-to-Beneficiary Information, or OBI) rides in memo. An unapproved destination returns 403 with code instrument_not_approved.

Authorizations:
bearerAuth
Request Body schema: application/json
required
idempotency_key
required
string [ 1 .. 255 ] characters .*\S.*

Use a non-blank random value minted once for this create request so retries do not create duplicate transfers.

required
object (TransferSource)

Where a transfer pulls money from. This must be the account associated with the API key or one of its sub-accounts. In v1 this is a Pocket account. Supply exactly one key.

required
object (InstrumentReference)

An external instrument ref (epi_…). For withdrawals, the instrument must belong to a source account in the hierarchy associated with the API key.

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, "100.00" means 100 USD.

  • The value must be greater than zero.
  • USD supports 1–16 integer digits and at most two fractional digits.
  • The maximum value is "9999999999999999.99".
  • Signs, commas, whitespace, scientific notation, leading-dot forms, trailing-dot forms, and unnecessary leading zeros are invalid.
  • The complete value may contain at most 19 characters.
  • Decimal-equivalent forms such as "1", "1.0", and "1.00" produce the same idempotency request fingerprint.

An invalid, non-positive or overflowing amount returns 400 invalid_request. param names the request field, such as amount, source_amount or items[n].amount. Pocket creates or changes nothing and does not bind the idempotency key. Other account and rail limits may impose a lower maximum.

currency
required
string (Currency)
Value: "USD"

Currency debited from or credited to the Pocket account.

  • USD is the complete supported set in v1.
  • Adding a value requires a coordinated specification and client update.
  • Cross-currency payouts may define separate destination-currency and foreign exchange (FX) fields.
reference
string <= 255 characters

Your private external reference for this transfer. Optional, not unique; filter by it. Counterparties do not see it.

memo
string <= 140 characters

Wire OBI/remittance information. Must be 140 characters or fewer; longer values are rejected with 400 (param is memo) and are never truncated.

object (Metadata) <= 50 properties

Up to 50 caller-defined string key/value pairs. An empty object is valid.

Keys

  • 1–64 Unicode characters.
  • Compared exactly as supplied.
  • Not trimmed, case-folded, or Unicode-normalized.
  • No additional character-set restriction.

OpenAPI 3.0 cannot express the key-length limit for arbitrary properties, so Pocket enforces it at runtime. An invalid key returns 400 invalid_request with param=metadata. The key is not echoed.

Values

  • String values only.
  • Empty values are allowed.
  • Maximum 256 Unicode characters.
  • Preserved exactly as supplied, without trimming or Unicode normalization.

An overlong value returns 400 invalid_request. param is metadata.<key> when the key is valid, or metadata when the key is invalid. The value is never echoed.

Privacy

Metadata is visible only from the account-owner side on which it was set and is not shared with money-movement counterparties. Use it for opaque business identifiers and non-sensitive labels only.

Do not include:

  • Government identity values.
  • Bank-account, routing, or payment-card details.
  • Passwords, secrets, access tokens, or credentials.
  • KYC documents.
  • Biometric or health information.
  • Names, email addresses, or phone numbers when a dedicated field exists.

Pocket may reject metadata that appears sensitive, but the caller remains responsible for the data it supplies.

Responses

Request samples

Content type
application/json
{
  • "idempotency_key": "e829e742-588b-4089-a048-a3d404401f91",
  • "source": {
    },
  • "destination": {
    },
  • "amount": "500.00",
  • "currency": "USD"
}

Response samples

Content type
application/json
{
  • "id": "txn_01jhv705whdehqd34b4113r1q8",
  • "method": "ach",
  • "reference": "payrun-2026-07",
  • "memo": "July salary",
  • "source": {
    },
  • "destination": {
    },
  • "batch_id": "string",
  • "funding_rule_id": "string",
  • "amount": "100.00",
  • "currency": "USD",
  • "status": "pending",
  • "failure_code": "string",
  • "failure_message": "string",
  • "fx": {
    },
  • "details": {
    },
  • "metadata": {
    },
  • "transitioned_at": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

Create an IBFT transfer (USD-to-PKR withdrawal)

Required scope: transfers:write.

Convert USD and withdraw PKR through Pocket's instant Inter Bank Funds Transfer (IBFT) route. This is self-withdrawal only. V1 does not expose the standard PKR cash-out route or promise a delivery time.

  • The destination must be an approved PKR bank instrument that belongs to source.account_id.
  • A business cannot use another sub-account's instrument or a rostered customer's instrument.
  • destination_currency must be PKR and must match the instrument.
  • Remittance classification is assigned by Pocket. V1 does not accept a caller-supplied purpose or beneficiary-visible memo.

Quoted pricing

Use pricing.mode=quote with an open fxq_…. The request must match the quote's source, destination, amount and currencies. A mismatch returns 400 quote_mismatch, with param naming the field. An open firm quote is not repriced, so pricing_unavailable does not apply.

Current pricing

Use pricing.mode=current to price and accept the transfer in one request. Pocket returns the quote ID in fx.quote_id. Set minimum_destination_amount to reject a quote below a chosen PKR amount. If pricing is unavailable, the operation returns 503 pricing_unavailable. A failed request creates neither the quote nor the transfer.

The gross source amount must exceed its source-currency fees. Otherwise, the operation returns 400 invalid_request with param=amount and creates nothing.

Atomic acceptance

Quote acceptance, transfer creation, the gross USD reservation, quote linkage and idempotency binding succeed or fail together. Replay is checked before quote state, so a successful request can be replayed without receiving quote_already_accepted.

Account and portfolio limits are checked when the transfer is created, not when a quote is created. 409 limit_exceeded creates no transfer and leaves the transfer idempotency key unbound. In current-pricing mode, it also creates no quote.

A quote-state conflict also creates no transfer and leaves the key unbound. Retry the same request with that key. A replacement quote changes the financial instruction and needs a new key.

At acceptance, the gross USD amount moves from available to reserved and the transfer begins pending. It becomes settled only when the beneficiary bank accepts the payment; an uncertain bank outcome remains pending.

The Transfer's amount equals fx.source_amount, and its currency equals fx.source_currency (USD in v1). fx.destination_amount is the PKR amount delivered to the destination instrument. FX fees are included within the gross USD amount, not added on top.

details is omitted until Pocket assigns a Retrieval Reference Number (RRN). Once present, details.ibft.retrieval_reference_number is always set.

A known failure releases the full gross USD reservation, including fees. Pocket does not retry a failed IBFT transfer. Obtain new pricing and create another transfer.

Authorizations:
bearerAuth
Request Body schema: application/json
required
idempotency_key
required
string [ 1 .. 255 ] characters .*\S.*

Use a non-blank random value minted once for this transfer request so retries do not create duplicate transfers.

required
object (TransferSource)

Where a transfer pulls money from. This must be the account associated with the API key or one of its sub-accounts. In v1 this is a Pocket account. Supply exactly one key.

required
object (InstrumentReference)

An external instrument ref (epi_…). For withdrawals, the instrument must belong to a source account in the hierarchy associated with the API key.

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, "100.00" means 100 USD.

  • The value must be greater than zero.
  • USD supports 1–16 integer digits and at most two fractional digits.
  • The maximum value is "9999999999999999.99".
  • Signs, commas, whitespace, scientific notation, leading-dot forms, trailing-dot forms, and unnecessary leading zeros are invalid.
  • The complete value may contain at most 19 characters.
  • Decimal-equivalent forms such as "1", "1.0", and "1.00" produce the same idempotency request fingerprint.

An invalid, non-positive or overflowing amount returns 400 invalid_request. param names the request field, such as amount, source_amount or items[n].amount. Pocket creates or changes nothing and does not bind the idempotency key. Other account and rail limits may impose a lower maximum.

currency
required
string (Currency)
Value: "USD"

Currency debited from or credited to the Pocket account.

  • USD is the complete supported set in v1.
  • Adding a value requires a coordinated specification and client update.
  • Cross-currency payouts may define separate destination-currency and foreign exchange (FX) fields.
destination_currency
required
string (DestinationCurrency)
Value: "PKR"

Currency delivered by a cross-currency transfer. USD-to-PKR IBFT is the only supported cross-currency corridor in v1.

required
any (TransferPricing)
reference
string <= 255 characters

Your private external reference for this transfer. Optional, not unique, and not sent to the beneficiary bank.

object (Metadata) <= 50 properties

Up to 50 caller-defined string key/value pairs. An empty object is valid.

Keys

  • 1–64 Unicode characters.
  • Compared exactly as supplied.
  • Not trimmed, case-folded, or Unicode-normalized.
  • No additional character-set restriction.

OpenAPI 3.0 cannot express the key-length limit for arbitrary properties, so Pocket enforces it at runtime. An invalid key returns 400 invalid_request with param=metadata. The key is not echoed.

Values

  • String values only.
  • Empty values are allowed.
  • Maximum 256 Unicode characters.
  • Preserved exactly as supplied, without trimming or Unicode normalization.

An overlong value returns 400 invalid_request. param is metadata.<key> when the key is valid, or metadata when the key is invalid. The value is never echoed.

Privacy

Metadata is visible only from the account-owner side on which it was set and is not shared with money-movement counterparties. Use it for opaque business identifiers and non-sensitive labels only.

Do not include:

  • Government identity values.
  • Bank-account, routing, or payment-card details.
  • Passwords, secrets, access tokens, or credentials.
  • KYC documents.
  • Biometric or health information.
  • Names, email addresses, or phone numbers when a dedicated field exists.

Pocket may reject metadata that appears sensitive, but the caller remains responsible for the data it supplies.

Responses

Request samples

Content type
application/json
{
  • "idempotency_key": "b2e6d1f0-3c44-4d55-8f66-77a889900abc",
  • "source": {
    },
  • "destination": {
    },
  • "amount": "1000.00",
  • "currency": "USD",
  • "destination_currency": "PKR",
  • "pricing": {
    },
  • "reference": "cashout-1042"
}

Response samples

Content type
application/json
{
  • "id": "txn_01jhv705whdehqd34b4113r1q8",
  • "method": "ach",
  • "reference": "payrun-2026-07",
  • "memo": "July salary",
  • "source": {
    },
  • "destination": {
    },
  • "batch_id": "string",
  • "funding_rule_id": "string",
  • "amount": "100.00",
  • "currency": "USD",
  • "status": "pending",
  • "failure_code": "string",
  • "failure_message": "string",
  • "fx": {
    },
  • "details": {
    },
  • "metadata": {
    },
  • "transitioned_at": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

Retrieve a transfer

Required scope: transfers:read.

Fetch one transfer by ID, e.g. /v1/transfers/txn_….

This returns the outbound side when its source account is in the account hierarchy associated with the API key. If the txn_… has no transfer side in that hierarchy, this endpoint returns 404.

For an internal movement, the same txn_… may also be retrievable from GET /v1/deposits/{deposit_id} when the inbound side is in the account hierarchy associated with the API key. The transfer is retrievable as soon as its txn_… exists and remains retrievable through its terminal status.

Authorizations:
bearerAuth
path Parameters
transfer_id
required
string^txn_
Example: txn_01jhv705whdehqd34b4113r1q8

Responses

Response samples

Content type
application/json
{
  • "id": "txn_01jhv705whdehqd34b4113r1q8",
  • "method": "ach",
  • "reference": "payrun-2026-07",
  • "memo": "July salary",
  • "source": {
    },
  • "destination": {
    },
  • "batch_id": "string",
  • "funding_rule_id": "string",
  • "amount": "100.00",
  • "currency": "USD",
  • "status": "pending",
  • "failure_code": "string",
  • "failure_message": "string",
  • "fx": {
    },
  • "details": {
    },
  • "metadata": {
    },
  • "transitioned_at": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

Deposits

Money arriving in an account.

Deposits are created for you, never by you:

  • An incoming ACH or other supported rail.
  • The destination view of an accepted internal transfer.
  • A Pocket-funded advance or reward.

A pending internal deposit is visible but does not affect the destination balance until settlement. Card refunds are feed-only transactions, not deposit objects.

List deposits

Required scope: deposits:read.

Inbound money movements across the account associated with the API key and its complete owned sub-account hierarchy.

  • Every method that produces a deposit object is included.
  • Card refunds are feed-only; see Transactions.
  • Deposits are read-only: you never create one.

Narrow results with method, account_id, status, amount, funding_rule_id, reference, created date, or settled date.

A destination view is listable as soon as its canonical txn_… exists.

For an internal movement:

  • The deposit appears immediately as pending.
  • It does not affect destination balances until settlement.
  • A later failure or void leaves the deposit visible with that terminal status.

When account_id is supplied, it selects one owned destination account.

Authorizations:
bearerAuth
query Parameters
method
Array of strings (DepositMethod)
Items Enum: "internal" "ach" "wire" "swift" "faster_payments" "payment_link"
Example: method=ach,wire

Filter deposits by method. Comma-separate to match more than one (e.g. method=ach,wire). A method that cannot produce a Deposit returns 400 invalid_request with param=method rather than an empty result.

account_id
string
Example: account_id=biz_01j8ma3wv3mepwabfxxs0yc395

Select the viewpoint of the account associated with the API key or one of its sub-accounts. Roster membership is not sufficient.

  • Malformed or wrong-prefix value: 400; param is account_id.
  • Well-formed non-owned account: 404.
  • Owned account with no list matches: empty page.
status
string (TransactionStatus)
Enum: "pending" "settled" "failed" "voided"

pending (accepted, not yet settled), settled (posted to the balance), failed, or voided. voided is platform-assigned and is not caller cancellation.

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. 100.00.

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. 100.00.

funding_rule_id
string

Only transfers or deposits initiated by this funding rule (frule_…).

reference
string

Exact match on your opaque reference for the object. Not unique; may match more than one object, and results are paginated.

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). Pending, failed, and voided items have no settlement time and are excluded when this filter is used.

settled_before
string <date-time>
Example: settled_before=2026-07-31T09:00:00Z

Only items settled at/before this time (inclusive). Pending, failed, and voided items have no settlement time and are excluded when this filter is used.

cursor
string

Opaque cursor (next_cursor from a prior page). Re-supply the same filters; a mismatch, expired cursor, or invalid cursor returns 400.

limit
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "has_more": true,
  • "next_cursor": "string",
  • "data": [
    ]
}

Retrieve a deposit

Required scope: deposits:read.

Fetch one deposit by ID, e.g. /v1/deposits/txn_….

This returns the inbound side when its destination account is in the account hierarchy associated with the API key. If the txn_… has no deposit side in that hierarchy, this endpoint returns 404.

For an internal movement:

  • The same txn_… may resolve at GET /v1/transfers/{transfer_id} when the outbound side is in the account hierarchy associated with the API key.
  • The deposit is retrievable immediately after transaction creation, including while pending.
  • It remains retrievable after becoming failed or voided.
Authorizations:
bearerAuth
path Parameters
deposit_id
required
string^txn_
Example: txn_01jjftqbz0n1vzey0pqrq3qmnn

Responses

Response samples

Content type
application/json
{
  • "id": "txn_01jjftqbz0n1vzey0pqrq3qmnn",
  • "method": "ach",
  • "account_id": "biz_01j8ma3wv3mepwabfxxs0yc395",
  • "source": null,
  • "destination": {
    },
  • "reference": null,
  • "memo": null,
  • "funding_rule_id": null,
  • "amount": "2500.00",
  • "currency": "USD",
  • "status": "settled",
  • "details": {
    },
  • "metadata": { },
  • "created_at": "2026-07-30T14:10:00Z",
  • "settled_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

FX Quotes

Price a cross-currency transfer before creating it.

V1 quotes are source-fixed USD-to-PKR quotes for method=ibft. A quote binds its source account, destination instrument, method, currencies, gross source amount, fees, rate, and resolved destination amount.

Lifecycle

  • open: available for one transfer to accept before expires_at.
  • accepted: linked permanently to the transfer in transfer_id.
  • expired: not accepted before expires_at.
  • canceled: withdrawn by Pocket before acceptance.

expires_at is exclusive: the quote cannot be accepted at or after that instant.

Terminal states do not change. An accepted quote never expires and is never reusable. Pocket may cancel an open quote, but while it remains open its quoted pricing is honored until expires_at. Acceptance can still fail because funds, authorization, limits, account state, or instrument approval changed.

Creating a quote does not reserve customer funds or affect balances. Multiple open quotes may coexist, subject to rate and capacity limits; creating one does not cancel another.

Create an FX quote

Required scope: transfers:write.

Request source-fixed pricing for a USD-to-PKR IBFT withdrawal. The quote binds all routing and pricing fields in the response.

  • The source must be the account associated with the API key or one of its sub-accounts.
  • The destination must be an approved PKR instrument owned by that same source account.
  • destination_currency must match the instrument's currency.
  • Creating a quote does not reserve USD or change a customer balance.
  • source_amount must exceed the total applicable source-currency fees. Otherwise Pocket returns 400 invalid_request; param is source_amount.
  • A quote may be accepted by exactly one transfer before expires_at; it cannot be accepted at or after that instant.

Pocket may cancel an open quote. While it remains open, market movement does not change its price. Funds, authorization, limits, account state and instrument approval are checked when the transfer is created. Quote creation does not return limit_exceeded.

Authorizations:
bearerAuth
Request Body schema: application/json
required
idempotency_key
required
string [ 1 .. 255 ] characters .*\S.*

Use a non-blank random value minted once for this quote request so retries do not create duplicate quotes.

required
object (TransferSource)

Where a transfer pulls money from. This must be the account associated with the API key or one of its sub-accounts. In v1 this is a Pocket account. Supply exactly one key.

required
object (InstrumentReference)

An external instrument ref (epi_…). For withdrawals, the instrument must belong to a source account in the hierarchy associated with the API key.

method
required
string
Value: "ibft"
source_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, "100.00" means 100 USD.

  • The value must be greater than zero.
  • USD supports 1–16 integer digits and at most two fractional digits.
  • The maximum value is "9999999999999999.99".
  • Signs, commas, whitespace, scientific notation, leading-dot forms, trailing-dot forms, and unnecessary leading zeros are invalid.
  • The complete value may contain at most 19 characters.
  • Decimal-equivalent forms such as "1", "1.0", and "1.00" produce the same idempotency request fingerprint.

An invalid, non-positive or overflowing amount returns 400 invalid_request. param names the request field, such as amount, source_amount or items[n].amount. Pocket creates or changes nothing and does not bind the idempotency key. Other account and rail limits may impose a lower maximum.

source_currency
required
string (Currency)
Value: "USD"

Currency debited from or credited to the Pocket account.

  • USD is the complete supported set in v1.
  • Adding a value requires a coordinated specification and client update.
  • Cross-currency payouts may define separate destination-currency and foreign exchange (FX) fields.
destination_currency
required
string (DestinationCurrency)
Value: "PKR"

Currency delivered by a cross-currency transfer. USD-to-PKR IBFT is the only supported cross-currency corridor in v1.

Responses

Request samples

Content type
application/json
{
  • "idempotency_key": "9f1c2a34-6a3d-4e0b-9c7e-2f8b1d5a7e21",
  • "source": {
    },
  • "destination": {
    },
  • "method": "ibft",
  • "source_amount": "1000.00",
  • "source_currency": "USD",
  • "destination_currency": "PKR"
}

Response samples

Content type
application/json
{
  • "id": "fxq_01k0f8ma3wv3mepwabfxxs0yc3",
  • "status": "open",
  • "method": "ibft",
  • "source": {
    },
  • "destination": {
    },
  • "fixed_side": "source",
  • "source_amount": "1000.00",
  • "source_currency": "USD",
  • "fees": [
    ],
  • "net_source_amount": "990.00",
  • "rate": "280.00",
  • "destination_amount": "277200.00",
  • "destination_currency": "PKR",
  • "transfer_id": null,
  • "created_at": "2026-07-13T09:00:00Z",
  • "expires_at": "2026-07-13T09:05:00Z",
  • "accepted_at": null,
  • "canceled_at": null
}

Retrieve an FX quote

Required scope: transfers:read.

Retrieve a quote whose source account is in the account hierarchy associated with the API key. A quote outside that hierarchy returns 404.

At or after expires_at, Pocket returns a stored open quote as expired. An accepted quote never becomes expired.

Expired and canceled quotes remain retrievable for at least 24 hours after creation, the same minimum period as their idempotency-key binding. Accepted quotes remain retrievable for as long as their accepting transfer.

Authorizations:
bearerAuth
path Parameters
quote_id
required
string^fxq_
Example: fxq_01k0f8ma3wv3mepwabfxxs0yc3

Responses

Response samples

Content type
application/json
{
  • "id": "fxq_01k0f8ma3wv3mepwabfxxs0yc3",
  • "status": "open",
  • "method": "ibft",
  • "source": {
    },
  • "destination": {
    },
  • "fixed_side": "source",
  • "source_amount": "1000.00",
  • "source_currency": "USD",
  • "fees": [
    ],
  • "net_source_amount": "990.00",
  • "rate": "280.00",
  • "destination_amount": "277200.00",
  • "destination_currency": "PKR",
  • "transfer_id": null,
  • "created_at": "2026-07-13T09:00:00Z",
  • "expires_at": "2026-07-13T09:05:00Z",
  • "accepted_at": null,
  • "canceled_at": null
}

Batch Transfers

Submit independent internal transfers from one source account in a single request.

Create a batch of transfers

Required scopes: batches:write and transfers:write.

Submit internal transfers from one owned source account as an asynchronous job. Acceptance does not move or reserve money.

Pocket validates the request and may run an optimistic balance check before accepting it.

  • Advisory checks do not reserve funds or guarantee settlement.
  • Each item checks available balance when it runs.
  • Item processing order is not guaranteed.

Recipient policy pre-check

recipient_policy defaults to any_account. Set it to payable_members to check the complete batch against the source account's roster before acceptance:

  • Every destination must be a member of the roster belonging to source.account_id.
  • Parent and other account rosters are not inherited or consulted.
  • Every matched member must currently have capabilities.can_receive_transfers set to true.

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. Pocket creates nothing and leaves the idempotency key unbound. Correct the request and retry with the same key.

The recipient check is not repeated during item processing. Ordinary transfer rules still apply.

Track every submitted line with GET /v1/batches/{batch_id}/items.

  • A failure before transaction creation has transfer_id: null.
  • Once a txn_… exists, transfer_id identifies it through settlement, failure or voiding.
  • Insufficient funds at execution produces failure_code: insufficient_funds.
  • Retry failed lines in a new batch. item_id does not deduplicate across batches; resubmitting a settled line can pay twice.

Duplicate item ids

Every item_id must be unique within items.

  • Comparison is exact and case-sensitive.
  • Pocket does not trim, case-fold, or normalize the value.
  • Other line fields do not affect duplicate detection.
  • reference and idempotency_key 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.

Pocket creates nothing and leaves the idempotency key unbound. Correct the request and retry with the same key.

Batches cannot be canceled once accepted.

The UTF-8 JSON body may be at most 10 MiB (10,485,760 bytes) instead of the usual 1 MiB. The whole request must fit, even when every item is valid. An oversized request returns 413 and creates nothing.

Authorizations:
bearerAuth
Request Body schema: application/json
required
idempotency_key
required
string [ 1 .. 255 ] characters .*\S.*

Use a non-blank random value minted once for this create request so retries do not create duplicate batches.

required
object (TransferSource)

Where a transfer pulls money from. This must be the account associated with the API key or one of its sub-accounts. In v1 this is a Pocket account. Supply exactly one key.

recipient_policy
string (BatchRecipientPolicy)
Default: "any_account"
Enum: "any_account" "payable_members"

One-time destination pre-check applied before Pocket accepts a batch.

  • any_account: skip the member-roster pre-check. Each destination may be any valid Pocket account.
  • payable_members: every destination must be a member of source.account_id's own roster and currently have capabilities.can_receive_transfers set to true. Parent and other account rosters are not consulted.

The policy is not evaluated again after acceptance or as individual transfers execute. Omitting the field and explicitly sending any_account are equivalent when Pocket computes the idempotency request fingerprint.

required
Array of objects (BatchTransferItem) [ 1 .. 4096 ] items

Each item_id must be unique in the array. Pocket enforces this at runtime because OpenAPI 3.0 uniqueItems compares whole objects rather than one field.

reference
string <= 255 characters

Your external ID for this batch (e.g. a pay-run ID). Optional, filterable.

object (Metadata) <= 50 properties

Up to 50 caller-defined string key/value pairs. An empty object is valid.

Keys

  • 1–64 Unicode characters.
  • Compared exactly as supplied.
  • Not trimmed, case-folded, or Unicode-normalized.
  • No additional character-set restriction.

OpenAPI 3.0 cannot express the key-length limit for arbitrary properties, so Pocket enforces it at runtime. An invalid key returns 400 invalid_request with param=metadata. The key is not echoed.

Values

  • String values only.
  • Empty values are allowed.
  • Maximum 256 Unicode characters.
  • Preserved exactly as supplied, without trimming or Unicode normalization.

An overlong value returns 400 invalid_request. param is metadata.<key> when the key is valid, or metadata when the key is invalid. The value is never echoed.

Privacy

Metadata is visible only from the account-owner side on which it was set and is not shared with money-movement counterparties. Use it for opaque business identifiers and non-sensitive labels only.

Do not include:

  • Government identity values.
  • Bank-account, routing, or payment-card details.
  • Passwords, secrets, access tokens, or credentials.
  • KYC documents.
  • Biometric or health information.
  • Names, email addresses, or phone numbers when a dedicated field exists.

Pocket may reject metadata that appears sensitive, but the caller remains responsible for the data it supplies.

Responses

Request samples

Content type
application/json
{
  • "idempotency_key": "a15e4ae4-52f3-460c-9c1c-5f6b7b90cc44",
  • "source": {
    },
  • "recipient_policy": "payable_members",
  • "items": [
    ],
  • "reference": "northwind-2026-07",
  • "metadata": {
    }
}

Response samples

Content type
application/json
{
  • "id": "batch_01j5gr81vpavng9emxaepf2be3",
  • "reference": "northwind-2026-07",
  • "source": {
    },
  • "recipient_policy": "any_account",
  • "status": "pending",
  • "item_counts": {
    },
  • "amount_totals": [
    ],
  • "metadata": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "finalized_at": "2026-07-31T09:00:00Z"
}

List batches

Required scope: batches:read.

List batches whose source is the account associated with the API key or one of its sub-accounts, recursively.

Use source_id to restrict the list to one owned source account.

Authorizations:
bearerAuth
query Parameters
source_id
string

Filter by an owned source account ID (biz_…/cus_…). A malformed or wrong-prefix value returns 400; param is source_id. A well-formed non-owned account returns 404; an owned account with no matches returns an empty page.

status
string (BatchStatus)
Enum: "pending" "processing" "completed" "partially_completed" "failed"

Summary status derived from the batch item counts:

  • pending: item_counts.pending == item_counts.total.
  • processing: 0 < item_counts.pending < item_counts.total.
  • completed: item_counts.settled == item_counts.total.
  • partially_completed: item_counts.pending == 0 and some, but not all, items settled.
  • failed: terminal with zero settled items; items may be failed or voided.

The counts are the source of truth for reconciliation and always satisfy item_counts.pending + item_counts.settled + item_counts.failed + item_counts.voided == item_counts.total.

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

Exact match on your opaque reference for the object. Not unique; may match more than one object, and results are paginated.

cursor
string

Opaque cursor (next_cursor from a prior page). Re-supply the same filters; a mismatch, expired cursor, or invalid cursor returns 400.

limit
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "has_more": true,
  • "next_cursor": "string",
  • "data": [
    ]
}

Retrieve a batch

Required scope: batches:read.

Retrieve a batch by ID. Use its item counts and amount totals to track progress.

Authorizations:
bearerAuth
path Parameters
batch_id
required
string
Example: batch_01j5gr81vpavng9emxaepf2be3

Responses

Response samples

Content type
application/json
{
  • "id": "batch_01j5gr81vpavng9emxaepf2be3",
  • "reference": "northwind-2026-07",
  • "source": {
    },
  • "recipient_policy": "any_account",
  • "status": "pending",
  • "item_counts": {
    },
  • "amount_totals": [
    ],
  • "metadata": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "finalized_at": "2026-07-31T09:00:00Z"
}

List a batch's items

Required scope: batches:read.

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 transfer, transfer_id links to the resulting txn_…; otherwise transfer_id is null and failure_code explains the failure.

Narrow with item_id, status, or reference.

To retry failures, submit the failed lines in a new batch. A new batch creates new transfers; item_id does not deduplicate across batches, so resubmitting a settled line can pay the recipient twice.

Authorizations:
bearerAuth
path Parameters
batch_id
required
string
query Parameters
item_id
string [ 1 .. 255 ] characters .*\S.*

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. When a transfer exists, this mirrors the transfer status. A voided item was voided by Pocket, will not settle, and is not a failure; failure_code is not set.

reference
string

Exact match on your opaque reference for the object. Not unique; may match more than one object, and results are paginated.

cursor
string

Opaque cursor (next_cursor from a prior page). Re-supply the same filters; a mismatch, expired cursor, or invalid cursor returns 400.

limit
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "has_more": true,
  • "next_cursor": "string",
  • "data": [
    ]
}

List a batch's transfers

Required scopes: batches:read and transfers:read.

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.

Authorizations:
bearerAuth
path Parameters
batch_id
required
string
query Parameters
status
string (TransferStatus)
Enum: "pending" "settled" "failed" "voided"

pending (accepted, not yet settled), settled (posted to the balance), failed, or voided. voided is assigned by Pocket for an accepted transfer that will not settle; there is no public cancel or void endpoint.

cursor
string

Opaque cursor (next_cursor from a prior page). Re-supply the same filters; a mismatch, expired cursor, or invalid cursor returns 400.

limit
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "has_more": true,
  • "next_cursor": "string",
  • "data": [
    ]
}

Accounts

Accounts and balances in the hierarchy associated with the API key.

Create an account

Required scope: accounts:write.

Create an active business sub-account. Omit parent_id to create it directly under the account associated with the API key. Otherwise, parent_id must name a business account in that hierarchy.

The public API sets no fixed hierarchy depth. Accounts may be nested while their state permits children.

  • Malformed or wrong-prefix parent_id: 400; param is parent_id.
  • Unknown or non-owned parent_id: 404.
  • Owned parent that cannot accept children in its current state: 409.
Authorizations:
bearerAuth
Request Body schema: application/json
required
idempotency_key
required
string [ 1 .. 255 ] characters .*\S.*

Use a non-blank random value minted once for this create request so retries do not create duplicate accounts. To carry your own ID for the account, use reference.

reference
string <= 255 characters

An opaque external reference of your choosing for this account. We don't parse or dedupe it; find accounts and balances by it with the reference query filter (which may return more than one).

name
string
parent_id
string

Parent business account (biz_…) to create under. It must be the account associated with the API key or one of its sub-accounts. Omit to use the associated account. Accounts outside the hierarchy return 404.

object (Metadata) <= 50 properties

Up to 50 caller-defined string key/value pairs. An empty object is valid.

Keys

  • 1–64 Unicode characters.
  • Compared exactly as supplied.
  • Not trimmed, case-folded, or Unicode-normalized.
  • No additional character-set restriction.

OpenAPI 3.0 cannot express the key-length limit for arbitrary properties, so Pocket enforces it at runtime. An invalid key returns 400 invalid_request with param=metadata. The key is not echoed.

Values

  • String values only.
  • Empty values are allowed.
  • Maximum 256 Unicode characters.
  • Preserved exactly as supplied, without trimming or Unicode normalization.

An overlong value returns 400 invalid_request. param is metadata.<key> when the key is valid, or metadata when the key is invalid. The value is never echoed.

Privacy

Metadata is visible only from the account-owner side on which it was set and is not shared with money-movement counterparties. Use it for opaque business identifiers and non-sensitive labels only.

Do not include:

  • Government identity values.
  • Bank-account, routing, or payment-card details.
  • Passwords, secrets, access tokens, or credentials.
  • KYC documents.
  • Biometric or health information.
  • Names, email addresses, or phone numbers when a dedicated field exists.

Pocket may reject metadata that appears sensitive, but the caller remains responsible for the data it supplies.

Responses

Request samples

Content type
application/json
{
  • "idempotency_key": "9ff42f6e-ec45-45ac-8f8b-5f5c2f1d0b4a",
  • "reference": "northwind-co",
  • "name": "Northwind Trading Co"
}

Response samples

Content type
application/json
{
  • "id": "biz_01j8ma3wv3mepwabfxxs0yc395",
  • "reference": "northwind-co",
  • "name": "string",
  • "parent_id": "string",
  • "status": "active",
  • "metadata": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

List accounts

Required scope: accounts:read.

List the account associated with the API key and all of its sub-accounts. A customer on a business member roster is included only if its account independently belongs to that hierarchy.

When parent_id is supplied, only that owned account's direct children are returned. The parent and grandchildren are excluded.

Authorizations:
bearerAuth
query Parameters
parent_id
string

List only the direct children of this owned business account (biz_…). The parent and grandchildren are not returned. Omit for the complete owned hierarchy.

  • Malformed or wrong-prefix value: 400; param is parent_id.
  • Well-formed non-owned account: 404.
  • Owned account with no matching children: empty page.
status
string (AccountStatus)
Enum: "active" "pending" "suspended" "closed"

Current account lifecycle state. pending is platform-owned while onboarding or review is incomplete. closed is terminal and cannot be reopened through this API.

reference
string

Exact match on your opaque reference for the object. Not unique; may match more than one object, and results are paginated.

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).

cursor
string

Opaque cursor (next_cursor from a prior page). Re-supply the same filters; a mismatch, expired cursor, or invalid cursor returns 400.

limit
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "has_more": true,
  • "next_cursor": "string",
  • "data": [
    ]
}

Retrieve an account

Required scope: accounts:read.

Retrieve the account associated with the API key or one of its sub-accounts. Roster membership alone does not make a customer account retrievable.

Authorizations:
bearerAuth
path Parameters
account_id
required
string
Example: biz_01j8ma3wv3mepwabfxxs0yc395

Responses

Response samples

Content type
application/json
{
  • "id": "biz_01j8ma3wv3mepwabfxxs0yc395",
  • "reference": "northwind-co",
  • "name": "string",
  • "parent_id": "string",
  • "status": "active",
  • "metadata": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

Update an account

Required scope: accounts:write.

Update a business sub-account you own.

  • Writable fields: name, status, and metadata.
  • Writable statuses: active and suspended.
  • pending is platform-owned.
  • closed is terminal; closing is not part of v1.
  • Recipient (cus_…) accounts are not mutable through this API.

Supply at least one writable field. An empty body or a body containing only idempotency_key returns 400 invalid_request.

If every supplied value already matches, Pocket returns the account without changing updated_at. A supplied idempotency_key is still bound to the response.

An illegal state transition returns 409.

Authorizations:
bearerAuth
path Parameters
account_id
required
string
Example: biz_01j8ma3wv3mepwabfxxs0yc395
Request Body schema: application/json
required
Any of
name
required
string
idempotency_key
string [ 1 .. 255 ] characters .*\S.*

Optional. When supplied, it must not be blank. Makes this update safe to retry; reusing the same key with a different request fingerprint returns 409 (see Idempotency).

status
string (AccountWritableStatus)
Enum: "active" "suspended"

Status values callers can set with PATCH /v1/accounts/{account_id}. pending and closed are platform-owned response states and cannot be supplied.

object (Metadata) <= 50 properties

Up to 50 caller-defined string key/value pairs. An empty object is valid.

Keys

  • 1–64 Unicode characters.
  • Compared exactly as supplied.
  • Not trimmed, case-folded, or Unicode-normalized.
  • No additional character-set restriction.

OpenAPI 3.0 cannot express the key-length limit for arbitrary properties, so Pocket enforces it at runtime. An invalid key returns 400 invalid_request with param=metadata. The key is not echoed.

Values

  • String values only.
  • Empty values are allowed.
  • Maximum 256 Unicode characters.
  • Preserved exactly as supplied, without trimming or Unicode normalization.

An overlong value returns 400 invalid_request. param is metadata.<key> when the key is valid, or metadata when the key is invalid. The value is never echoed.

Privacy

Metadata is visible only from the account-owner side on which it was set and is not shared with money-movement counterparties. Use it for opaque business identifiers and non-sensitive labels only.

Do not include:

  • Government identity values.
  • Bank-account, routing, or payment-card details.
  • Passwords, secrets, access tokens, or credentials.
  • KYC documents.
  • Biometric or health information.
  • Names, email addresses, or phone numbers when a dedicated field exists.

Pocket may reject metadata that appears sensitive, but the caller remains responsible for the data it supplies.

Responses

Request samples

Content type
application/json
{
  • "metadata": {
    },
  • "idempotency_key": "string",
  • "name": "string",
  • "status": "active"
}

Response samples

Content type
application/json
{
  • "id": "biz_01j8ma3wv3mepwabfxxs0yc395",
  • "reference": "northwind-co",
  • "name": "string",
  • "parent_id": "string",
  • "status": "active",
  • "metadata": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

List an account's balances

Required scope: balances:read.

Return one funding balance per currency for a single account. The account may be the one associated with the API key or any of its sub-accounts. A roster relationship alone does not make a customer account readable.

Only USD is supported in v1, so the response contains at most one item and has_more is always false.

The balance is the payable pool, not a net position. Funds still moving through an external rail, or otherwise unavailable for payment, are not included. Filtering to a currency the account does not hold returns an empty page, not a 404.

Authorizations:
bearerAuth
path Parameters
account_id
required
string
Example: biz_01j8ma3wv3mepwabfxxs0yc395
query Parameters
currency
string (Currency)
Value: "USD"
Example: currency=USD

Only the balance in this currency.

Responses

Response samples

Content type
application/json
{
  • "has_more": true,
  • "next_cursor": "string",
  • "data": [
    ]
}

List balances across accounts

Required scope: balances:read.

One entry per account per currency across the account associated with the API key and its complete owned sub-account hierarchy, recursively. Rostered customer accounts are not included unless they are independently part of that hierarchy.

When parent_id is supplied, only balances for that owned account's direct children are returned. The parent and grandchildren are excluded.

For a single account, use List an account's balances.

Authorizations:
bearerAuth
query Parameters
parent_id
string

List balances only for the direct children of this owned business account (biz_…). The parent and grandchildren are not returned. Omit for the complete owned hierarchy.

  • Malformed or wrong-prefix value: 400; param is parent_id.
  • Well-formed non-owned account: 404.
  • Owned account with no matching child balances: empty page.
currency
string (Currency)
Value: "USD"
Example: currency=USD

Only balances in this currency.

reference
string

Exact match on your opaque reference for the object. Not unique; may match more than one object, and results are paginated.

cursor
string

Opaque cursor (next_cursor from a prior page). Re-supply the same filters; a mismatch, expired cursor, or invalid cursor returns 400.

limit
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "has_more": true,
  • "next_cursor": "string",
  • "data": [
    ]
}

Instruments

External accounts registered to the account associated with the API key or one of its sub-accounts, used as transfer destinations. USD instruments support ACH and wire withdrawals; PKR Pakistani bank instruments support self-withdrawal by IBFT.

List supported banks

Required scope: instruments:read.

List banks Pocket supports for external instruments. Use code as bank_account.bank_code when registering a Pakistani bank account.

Bank support can change. After registration, store the instrument ID instead of relying on this list as a permanent eligibility guarantee.

This endpoint is unpaginated in v1 but uses the standard list shape. has_more is always false, and next_cursor is always JSON null.

Authorizations:
bearerAuth
query Parameters
country
required
string
Value: "PK"

ISO 3166-1 alpha-2 country code. PK is supported in v1.

method
string
Value: "ibft"

Limit the result to banks that support this transfer method.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": false,
  • "next_cursor": null
}

Register an external instrument

Required scope: instruments:write.

Register an external bank account as a withdrawal destination for account_id.

account_id must be the account associated with the API key or one of its sub-accounts; a business cannot register instruments on a rostered customer account outside that hierarchy. The instrument must be approved before a transfer can use it.

For a Pakistani PKR instrument, Pocket verifies the bank account and expected holder identity. It does not expose the bank-returned title, title-match score or decline reason.

Local account-number rules vary by Pakistani bank and are validated during registration. An invalid local account number or IBAN returns 400 invalid_request; param is bank_account.account_number.

Authorizations:
bearerAuth
path Parameters
account_id
required
string
Example: biz_01j8ma3wv3mepwabfxxs0yc395
Request Body schema: application/json
required
One of
currency
required
string
Enum: "USD" "PKR"

Currency accepted by an external instrument.

This is separate from Currency: PKR instruments can receive a resolved withdrawal amount, but Pocket does not expose a PKR account balance in v1.

required
object

US bank account details. Raw account and routing values are accepted only on create and are not returned by read endpoints.

idempotency_key
required
string [ 1 .. 255 ] characters .*\S.*

Use a non-blank random value minted once for this create request so retries do not create duplicate instruments.

reference
string <= 255 characters

Your external ID for this instrument (optional, filterable).

type
required
string (InstrumentType)
Value: "bank_account"
object (Metadata) <= 50 properties

Up to 50 caller-defined string key/value pairs. An empty object is valid.

Keys

  • 1–64 Unicode characters.
  • Compared exactly as supplied.
  • Not trimmed, case-folded, or Unicode-normalized.
  • No additional character-set restriction.

OpenAPI 3.0 cannot express the key-length limit for arbitrary properties, so Pocket enforces it at runtime. An invalid key returns 400 invalid_request with param=metadata. The key is not echoed.

Values

  • String values only.
  • Empty values are allowed.
  • Maximum 256 Unicode characters.
  • Preserved exactly as supplied, without trimming or Unicode normalization.

An overlong value returns 400 invalid_request. param is metadata.<key> when the key is valid, or metadata when the key is invalid. The value is never echoed.

Privacy

Metadata is visible only from the account-owner side on which it was set and is not shared with money-movement counterparties. Use it for opaque business identifiers and non-sensitive labels only.

Do not include:

  • Government identity values.
  • Bank-account, routing, or payment-card details.
  • Passwords, secrets, access tokens, or credentials.
  • KYC documents.
  • Biometric or health information.
  • Names, email addresses, or phone numbers when a dedicated field exists.

Pocket may reject metadata that appears sensitive, but the caller remains responsible for the data it supplies.

Responses

Request samples

Content type
application/json
Example
{
  • "currency": "USD",
  • "bank_account": {
    },
  • "idempotency_key": "string",
  • "reference": "string",
  • "type": "bank_account",
  • "metadata": {
    }
}

Response samples

Content type
application/json
{
  • "id": "epi_01jzjc7rmr2k988mgd9nrk1r1b",
  • "account_id": "biz_01j6qvsv30kfq9dq59sxp9aze5",
  • "reference": "string",
  • "type": "bank_account",
  • "status": "in_review",
  • "currency": "USD",
  • "country": "PK",
  • "bank_code": "hbl",
  • "bank_name": "HBL",
  • "bank_last4": "4242",
  • "holder_name": "string",
  • "metadata": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "approved_at": "2026-07-31T09:00:00Z"
}

List an account's instruments

Required scope: instruments:read.

List instruments for the account associated with the API key or one of its sub-accounts. Instruments attached to rostered customer accounts outside that hierarchy are private and are not listable by the business.

Soft-deleted instruments are excluded by default. Filter with status=deleted to include only deleted instruments.

Authorizations:
bearerAuth
path Parameters
account_id
required
string
Example: biz_01j8ma3wv3mepwabfxxs0yc395
query Parameters
status
string (InstrumentStatus)
Enum: "in_review" "approved" "declined" "deleted"

Instrument lifecycle status. Omit to exclude soft-deleted instruments.

reference
string

Exact match on your opaque reference for the object. Not unique; may match more than one object, and results are paginated.

cursor
string

Opaque cursor (next_cursor from a prior page). Re-supply the same filters; a mismatch, expired cursor, or invalid cursor returns 400.

limit
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "has_more": true,
  • "next_cursor": "string",
  • "data": [
    ]
}

Retrieve an instrument

Required scope: instruments:read.

Retrieve an instrument belonging to the account associated with the API key or one of its sub-accounts. Instruments attached to customer accounts outside that hierarchy remain private.

Direct retrieval continues to return a soft-deleted instrument with status=deleted so historical transfer references remain resolvable.

Authorizations:
bearerAuth
path Parameters
instrument_id
required
string
Example: epi_01jzjc7rmr2k988mgd9nrk1r1b

Responses

Response samples

Content type
application/json
{
  • "id": "epi_01jzjc7rmr2k988mgd9nrk1r1b",
  • "account_id": "biz_01j6qvsv30kfq9dq59sxp9aze5",
  • "reference": "string",
  • "type": "bank_account",
  • "status": "in_review",
  • "currency": "USD",
  • "country": "PK",
  • "bank_code": "hbl",
  • "bank_name": "HBL",
  • "bank_last4": "4242",
  • "holder_name": "string",
  • "metadata": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "approved_at": "2026-07-31T09:00:00Z"
}

Remove an instrument

Required scope: instruments:write.

Soft-delete an instrument belonging to the account associated with the API key or one of its sub-accounts. A business cannot remove instruments from a rostered customer account outside that hierarchy.

  • Deletion is terminal. A deleted instrument cannot receive withdrawals or be restored.
  • The first deletion and every repeated deletion return 204.
  • Direct retrieval continues to return the instrument with status=deleted.
  • Unfiltered lists exclude it; filter with status=deleted to retrieve deleted instruments.
  • Historical transfers may continue to reference the instrument.
Authorizations:
bearerAuth
path Parameters
instrument_id
required
string

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "destination.account_id has an unexpected prefix",
  • "param": "destination.account_id",
  • "request_id": "req_01jj0m9w2k3n5p7q9r1s3t5v7w"
}

Members

Members are the customers (cus_…) a business keeps on its payment roster.

  • A member is only a reference to a Pocket customer account.
  • Membership may link an existing customer or trigger an invitation.
  • Membership never grants access to or changes the customer account.
  • Full government identity values are accepted only as create/search inputs.
  • Full values are never returned or included in errors, logs, traces, metadata, events, or webhook subjects.

Identity inputs are normalized before matching:

  • customer_id: exact Pocket cus_… ID.
  • phone: E.164 international phone-number format.
  • email: surrounding whitespace trimmed, then compared case-insensitively using Pocket's canonical form.
  • government_id: a Pakistani Computerized National Identity Card (CNIC), accepted as 13 digits or #####-#######-#.

For CNICs, Pocket ignores surrounding whitespace and display hyphens, then compares the 13 digits. A malformed identifier returns 400 with param naming the field.

Add a member to a business

Required scope: members:write.

Add a customer to this business's payment roster. Name lookup is not supported.

Identifiers

Supply any combination of customer_id, email, phone and government_id. At least one is required. If several are supplied, they must all resolve to the same customer. A mismatch returns a generic 400 without revealing which values matched.

Existing customer

Pocket links an existing customer immediately and returns customer_id. The customer does not need to consent to each business relationship.

  • active: the customer completed the relevant onboarding.
  • onboarding_required: onboarding is incomplete.

In either state, use capabilities.can_receive_transfers to decide whether the member can be paid.

No existing match

If phone or email is present, Pocket can create a pending customer and the roster relationship. The response contains a new customer_id, status=onboarding_required and can_receive_transfers=false.

Invitation delivery is separate from this operation and is not guaranteed by it.

An unmatched government_id alone returns 404 because phone or email is required to create a new customer. An explicit unknown customer_id also returns 404; Pocket never substitutes another customer or creates a customer with a caller-supplied ID.

Retries and re-adding

This endpoint takes no idempotency_key. A retry that resolves to the same customer returns the existing relationship and does not resend an invitation. New relationships return 201 Created; existing relationships return 200 OK.

If the relationship already exists and is not removed, this operation does not update its metadata; use the member update endpoint instead.

Re-adding a removed member restores the relationship and returns 200 OK. Omitted metadata is preserved; supplied metadata replaces the previous object. The restored status is active when onboarding is complete and onboarding_required otherwise.

Privacy

The response contains roster data only. It does not grant access to the customer's account or any private customer data.

Pocket may apply additional rate limits to identity resolution and customer creation. Never log or trace raw identity inputs, and never copy a full government identity value into metadata.

Authorizations:
bearerAuth
path Parameters
account_id
required
string
Example: biz_01j8ma3wv3mepwabfxxs0yc395
Request Body schema: application/json
required
Any of
customer_id
required
string^cus_

Match by the customer's exact Pocket ID (cus_…); this value is not normalized.

email
string <email> (MemberEmailLookup)

Match by email after trimming surrounding whitespace and applying Pocket's case-insensitive canonical email normalization.

phone
string (MemberPhoneLookup) ^\+[1-9][0-9]{1,14}$

Match by E.164 phone (e.g. +923228884666).

government_id
string (MemberGovernmentIdLookup) ^\s*(?:[0-9]{13}|[0-9]{5}-[0-9]{7}-[0-9])\s*$...

Match by Pakistani CNIC, the government identity type supported in v1. Formatted and unformatted values are normalized to the same 13 digits before matching. Responses return only government_id_masked.

object (Metadata) <= 50 properties

Up to 50 caller-defined string key/value pairs. An empty object is valid.

Keys

  • 1–64 Unicode characters.
  • Compared exactly as supplied.
  • Not trimmed, case-folded, or Unicode-normalized.
  • No additional character-set restriction.

OpenAPI 3.0 cannot express the key-length limit for arbitrary properties, so Pocket enforces it at runtime. An invalid key returns 400 invalid_request with param=metadata. The key is not echoed.

Values

  • String values only.
  • Empty values are allowed.
  • Maximum 256 Unicode characters.
  • Preserved exactly as supplied, without trimming or Unicode normalization.

An overlong value returns 400 invalid_request. param is metadata.<key> when the key is valid, or metadata when the key is invalid. The value is never echoed.

Privacy

Metadata is visible only from the account-owner side on which it was set and is not shared with money-movement counterparties. Use it for opaque business identifiers and non-sensitive labels only.

Do not include:

  • Government identity values.
  • Bank-account, routing, or payment-card details.
  • Passwords, secrets, access tokens, or credentials.
  • KYC documents.
  • Biometric or health information.
  • Names, email addresses, or phone numbers when a dedicated field exists.

Pocket may reject metadata that appears sensitive, but the caller remains responsible for the data it supplies.

Responses

Request samples

Content type
application/json
{
  • "government_id": "string",
  • "phone": "+923228884666",
  • "metadata": {
    }
}

Response samples

Content type
application/json
{
  • "business_id": "biz_01j8ma3wv3mepwabfxxs0yc395",
  • "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej",
  • "email": "usama@example.com",
  • "phone": "+923228884666",
  • "government_id_masked": "*********5678",
  • "status": "active",
  • "capabilities": {
    },
  • "metadata": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

List a business's members

Required scope: members:read.

The roster of customers this business pays.

Only roster-visible fields are returned; customer account details and internal account status remain private.

  • Removed members are excluded by default.
  • Use status=removed to list removed relationships.
  • Filter by another status to list only relationships in that state.
  • Retrieve a member by customer_id from GET /v1/accounts/{account_id}/members/{customer_id}.
  • Find a member by email, phone, or government ID with the search endpoint.
Authorizations:
bearerAuth
path Parameters
account_id
required
string
Example: biz_01j8ma3wv3mepwabfxxs0yc395
query Parameters
status
string (MemberStatus)
Enum: "onboarding_required" "active" "inactive" "removed"

Only relationships in this state. Without this filter, removed relationships are excluded.

cursor
string

Opaque cursor (next_cursor from a prior page). Re-supply the same filters; a mismatch, expired cursor, or invalid cursor returns 400.

limit
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "has_more": true,
  • "next_cursor": "string",
  • "data": [
    ]
}

Find a member

Required scope: members:read.

Look up a roster member by customer_id, email, phone, government_id or any combination. Identifiers belong in the request body so personally identifiable information (PII) does not appear in URLs. Name lookup is not supported.

This endpoint searches this business's roster only.

  • Every resolved identifier must refer to the same customer.
  • Conflicting identifiers return a generic 400 without revealing which values matched.
  • No Pocket customer, a customer outside this roster, and a non-owned business account all return the same 404.

The response never confirms whether an identifier exists elsewhere in Pocket.

Although this is a POST, it creates nothing, takes no idempotency_key and is safe to retry. Pocket may apply additional rate limits to identity searches.

Never log or trace raw identity inputs, and never copy a full government identity value into metadata.

The response contains roster data only. It exposes no private customer or account data.

Authorizations:
bearerAuth
path Parameters
account_id
required
string
Example: biz_01j8ma3wv3mepwabfxxs0yc395
Request Body schema: application/json
required
Any of
customer_id
required
string^cus_

Match by the customer's exact Pocket ID (cus_…); this value is not normalized.

email
string <email> (MemberEmailLookup)

Match by email after trimming surrounding whitespace and applying Pocket's case-insensitive canonical email normalization.

phone
string (MemberPhoneLookup) ^\+[1-9][0-9]{1,14}$

Match by E.164 phone (e.g. +923228884666).

government_id
string (MemberGovernmentIdLookup) ^\s*(?:[0-9]{13}|[0-9]{5}-[0-9]{7}-[0-9])\s*$...

Match by Pakistani CNIC, the government identity type supported in v1. Formatted and unformatted values are normalized to the same 13 digits before matching. Responses return only government_id_masked.

Responses

Request samples

Content type
application/json
{
  • "government_id": "string",
  • "phone": "+923228884666"
}

Response samples

Content type
application/json
{
  • "business_id": "biz_01j8ma3wv3mepwabfxxs0yc395",
  • "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej",
  • "email": "usama@example.com",
  • "phone": "+923228884666",
  • "government_id_masked": "*********5678",
  • "status": "active",
  • "capabilities": {
    },
  • "metadata": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

Retrieve a member

Required scope: members:read.

Retrieve one roster member by customer_id. Soft-removed relationships are included. The response contains roster data only, not private customer or account data.

Authorizations:
bearerAuth
path Parameters
account_id
required
string
Example: biz_01j8ma3wv3mepwabfxxs0yc395
customer_id
required
string
Example: cus_01jm0defbbct46vh2ev5dkkhej

The linked customer (cus_…).

Responses

Response samples

Content type
application/json
{
  • "business_id": "biz_01j8ma3wv3mepwabfxxs0yc395",
  • "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej",
  • "email": "usama@example.com",
  • "phone": "+923228884666",
  • "government_id_masked": "*********5678",
  • "status": "active",
  • "capabilities": {
    },
  • "metadata": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

Update a member

Required scope: members:write.

Update this business's roster relationship. Supply at least one of status or metadata.

  • status may be active or inactive.
  • Supplied metadata replaces the whole metadata object; use {} to clear it.
  • An idempotency_key is optional but cannot be the only field.

This changes only the roster relationship. It does not change the Pocket customer or customer account.

active means the business considers the roster relationship active. It does not guarantee that the customer can receive a transfer.

  • Use capabilities.can_receive_transfers as the sole current inclusion check for roster-based payments.
  • An active update cannot bypass onboarding_required or private customer-account state.
  • A removed relationship cannot be restored with PATCH; re-add it through the member collection endpoint.
  • An unavailable state transition returns 409.
Authorizations:
bearerAuth
path Parameters
account_id
required
string
Example: biz_01j8ma3wv3mepwabfxxs0yc395
customer_id
required
string
Example: cus_01jm0defbbct46vh2ev5dkkhej

The linked customer (cus_…).

Request Body schema: application/json
required
Any of
status
required
string
Enum: "active" "inactive"

The roster states a business may request. Setting active changes only the roster relationship and cannot bypass business-visible onboarding or a private Pocket customer account state.

idempotency_key
string [ 1 .. 255 ] characters .*\S.*

Optional. When supplied, it must not be blank. Makes this update safe to retry; reusing the same key with a different request fingerprint returns 409 (see Idempotency).

object (Metadata) <= 50 properties

Up to 50 caller-defined string key/value pairs. An empty object is valid.

Keys

  • 1–64 Unicode characters.
  • Compared exactly as supplied.
  • Not trimmed, case-folded, or Unicode-normalized.
  • No additional character-set restriction.

OpenAPI 3.0 cannot express the key-length limit for arbitrary properties, so Pocket enforces it at runtime. An invalid key returns 400 invalid_request with param=metadata. The key is not echoed.

Values

  • String values only.
  • Empty values are allowed.
  • Maximum 256 Unicode characters.
  • Preserved exactly as supplied, without trimming or Unicode normalization.

An overlong value returns 400 invalid_request. param is metadata.<key> when the key is valid, or metadata when the key is invalid. The value is never echoed.

Privacy

Metadata is visible only from the account-owner side on which it was set and is not shared with money-movement counterparties. Use it for opaque business identifiers and non-sensitive labels only.

Do not include:

  • Government identity values.
  • Bank-account, routing, or payment-card details.
  • Passwords, secrets, access tokens, or credentials.
  • KYC documents.
  • Biometric or health information.
  • Names, email addresses, or phone numbers when a dedicated field exists.

Pocket may reject metadata that appears sensitive, but the caller remains responsible for the data it supplies.

Responses

Request samples

Content type
application/json
{
  • "metadata": {
    },
  • "status": "inactive"
}

Response samples

Content type
application/json
{
  • "business_id": "biz_01j8ma3wv3mepwabfxxs0yc395",
  • "customer_id": "cus_01jm0defbbct46vh2ev5dkkhej",
  • "email": "usama@example.com",
  • "phone": "+923228884666",
  • "government_id_masked": "*********5678",
  • "status": "active",
  • "capabilities": {
    },
  • "metadata": {
    },
  • "created_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

Remove a member

Required scope: members:write.

Soft-remove a member from this business's roster by its customer_id.

  • The relationship becomes removed and can_receive_transfers becomes false.
  • It remains available through direct retrieval and GET .../members?status=removed.
  • It is excluded from unfiltered member lists.
  • Re-adding the customer restores this same relationship.

Removing a member does not affect the Pocket customer, account or payment history.

Authorizations:
bearerAuth
path Parameters
account_id
required
string
Example: biz_01j8ma3wv3mepwabfxxs0yc395
customer_id
required
string
Example: cus_01jm0defbbct46vh2ev5dkkhej

The linked customer (cus_…).

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "destination.account_id has an unexpected prefix",
  • "param": "destination.account_id",
  • "request_id": "req_01jj0m9w2k3n5p7q9r1s3t5v7w"
}

Funding Rules

Auto-route deposits from a parent business account into a sub-account.

Matching

  • When a settled deposit matches, its full amount moves to the rule's destination as an internal transfer.
  • Rules are evaluated oldest first.
  • The first matching rule wins.

Timing

  • A rule evaluates only deposits that reach settled while it is enabled.
  • Creating or re-enabling a rule does not scan earlier deposits.
  • Preview evaluates criteria only. It never routes or reserves money.

No match

The funds remain in the parent account. No transfer or balance reservation is created. Any preview or event reporting the result is informational only.

Failed routing transfer

A failure is terminal and Pocket does not retry automatically:

  • The failed transfer remains visible with its funding_rule_id.
  • Funds remain in the parent account.
  • The deposit is not evaluated again.
  • Later balance changes do not trigger another attempt.

Use an explicit internal transfer to move earlier, unmatched, or failed-routing funds.

Create a funding rule

Required scope: funding_rules:write.

Create a deposit-routing rule.

  • The account in the path is the source and must be a parent business account.
  • Sub-accounts cannot hold rules.
  • destination_account_id must be an active direct sub-account of the source.
  • The source cannot also be the destination.

Malformed criteria or an invalid destination return 400 invalid_request, with param naming the field. A source that is not a parent business returns 403 forbidden.

Authorizations:
bearerAuth
path Parameters
account_id
required
string
Example: biz_01j8ma3wv3mepwabfxxs0yc395
Request Body schema: application/json
required
idempotency_key
required
string [ 1 .. 255 ] characters .*\S.*

Use a non-blank random value minted once for this create request so retries do not create duplicate rules.

destination_account_id
required
string

The sub-account matched deposits move to (biz_…).

required
Array of any (FundingRuleCriterion) non-empty

Responses

Request samples

Content type
application/json
{
  • "idempotency_key": "5af19ee3-f1f2-4147-b1b8-67a9fc80f7e2",
  • "destination_account_id": "biz_01j6qvsv30kfq9dq59sxp9aze5",
  • "criteria": [
    ]
}

Response samples

Content type
application/json
{
  • "id": "frule_01jzq3v9k2x8e4w7r5t1n6m0ps",
  • "source_account_id": "biz_01j8ma3wv3mepwabfxxs0yc395",
  • "destination_account_id": "biz_01j6qvsv30kfq9dq59sxp9aze5",
  • "criteria": [
    ],
  • "status": "enabled",
  • "version": 0,
  • "created_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

List an account's funding rules

Required scope: funding_rules:read.

List rules whose source is account_id. Deleted rules appear only when you filter with status=deleted.

Authorizations:
bearerAuth
path Parameters
account_id
required
string
Example: biz_01j8ma3wv3mepwabfxxs0yc395
query Parameters
status
string (FundingRuleStatus)
Enum: "enabled" "disabled" "deleted"

Only enabled rules are evaluated against incoming deposits. Disable a rule to pause it without losing it.

DELETE sets the terminal deleted state. Deleted rules stay retrievable by ID and appear in lists only with status=deleted.

cursor
string

Opaque cursor (next_cursor from a prior page). Re-supply the same filters; a mismatch, expired cursor, or invalid cursor returns 400.

limit
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "has_more": true,
  • "next_cursor": "string",
  • "data": [
    ]
}

Preview a funding rule

Required scopes: funding_rules:read and transactions:read.

Test draft criteria against recent settled deposits. Preview creates nothing and never moves or reserves money.

  • The lookback contains at most the 50 most recent settled deposits from the last 90 days.
  • Access requirements are the same as reading the account's transactions.
  • Author rules from the normalized preview values. Those are the values used during live matching.

For a deposit from the SWIFT international payment network, preview values may include method and sender_name. description is absent, so a criterion on it does not match. A SWIFT payment reference is not treated as a description.

Authorizations:
bearerAuth
path Parameters
account_id
required
string
Example: biz_01j8ma3wv3mepwabfxxs0yc395
Request Body schema: application/json
required
required
Array of any (FundingRuleCriterion) non-empty

Responses

Request samples

Content type
application/json
{
  • "criteria": [
    ]
}

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Retrieve a funding rule

Required scope: funding_rules:read.

Retrieve a rule by ID. Soft-deleted rules are included.

Authorizations:
bearerAuth
path Parameters
rule_id
required
string
Example: frule_01jzq3v9k2x8e4w7r5t1n6m0ps

Responses

Response samples

Content type
application/json
{
  • "id": "frule_01jzq3v9k2x8e4w7r5t1n6m0ps",
  • "source_account_id": "biz_01j8ma3wv3mepwabfxxs0yc395",
  • "destination_account_id": "biz_01j6qvsv30kfq9dq59sxp9aze5",
  • "criteria": [
    ],
  • "status": "enabled",
  • "version": 0,
  • "created_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

Update a funding rule

Required scope: funding_rules:write.

Update criteria, or pause/resume the rule with status.

  • Writable statuses are enabled and disabled.
  • Use DELETE to set deleted.
  • Deleted rules are terminal; updates to them return 409.
  • Supply at least one of criteria or status. An empty body or a body containing only idempotency_key returns 400 invalid_request.
  • Supply idempotency_key to make the update safe to retry.
  • A change to criteria or status increments version and changes updated_at.
  • If every supplied value already matches, Pocket returns the rule without changing version or updated_at. A supplied idempotency_key is still bound.

If the rule changes after a deposit matches but before its routing transfer runs, the transfer does not run. Funds stay in the source account, and matching is not retried under the new rule version.

Authorizations:
bearerAuth
path Parameters
rule_id
required
string
Example: frule_01jzq3v9k2x8e4w7r5t1n6m0ps
Request Body schema: application/json
required
Any of
required
Array of (FundingRuleTextCriterion (object) or FundingRuleMethodCriterion (object)) or (FundingRuleTextCriterion (object) or FundingRuleMethodCriterion (object)) non-empty
idempotency_key
string [ 1 .. 255 ] characters .*\S.*

Optional. When supplied, it must not be blank. Makes this update safe to retry; reusing the same key with a different request fingerprint returns 409 (see Idempotency).

status
string (FundingRuleWritableStatus)
Enum: "enabled" "disabled"

Status values callers can set with PATCH /v1/funding_rules/{rule_id}. Use DELETE to set deleted; deleted rules are terminal and cannot be re-enabled.

Responses

Request samples

Content type
application/json
{
  • "status": "enabled",
  • "idempotency_key": "string",
  • "criteria": [
    ]
}

Response samples

Content type
application/json
{
  • "id": "frule_01jzq3v9k2x8e4w7r5t1n6m0ps",
  • "source_account_id": "biz_01j8ma3wv3mepwabfxxs0yc395",
  • "destination_account_id": "biz_01j6qvsv30kfq9dq59sxp9aze5",
  • "criteria": [
    ],
  • "status": "enabled",
  • "version": 0,
  • "created_at": "2026-07-31T09:00:00Z",
  • "updated_at": "2026-07-31T09:00:00Z"
}

Delete a funding rule

Required scope: funding_rules:write.

Stop the rule immediately.

  • It no longer matches deposits.
  • It appears in lists only when filtering by status=deleted.
  • GET /v1/funding_rules/{rule_id} continues to return it with status set to deleted.

This soft deletion keeps past transfer funding_rule_id references resolvable.

Authorizations:
bearerAuth
path Parameters
rule_id
required
string
Example: frule_01jzq3v9k2x8e4w7r5t1n6m0ps

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "destination.account_id has an unexpected prefix",
  • "param": "destination.account_id",
  • "request_id": "req_01jj0m9w2k3n5p7q9r1s3t5v7w"
}

Events

Webhook endpoints and the event log (list, retrieve, replay).

Events are reference-only notifications across the account associated with the API key and its owned sub-account hierarchy. They cover:

  • Money activity, including transfers, deposits, and batches.
  • Business account and sub-account changes.
  • Membership lifecycle changes.
  • Funding-rule changes and evaluation outcomes.

An event never embeds a resource snapshot. Use its typed subject and optional related references to fetch current resource state.

Event type identifiers follow resource.event naming. They are open, opaque strings; clients must handle unknown values.

Events are retained for 7 days from created_at. Older events are omitted from lists and return 404 on retrieve or replay.

Create a webhook endpoint

Required scope: webhooks:write.

Create an endpoint that receives Pocket events.

  • The signing secret appears only in the original successful response.
  • An idempotent replay returns secret: null.
  • If the original response is lost, rotate the secret with a new key.
Authorizations:
bearerAuth
Request Body schema: application/json
required
idempotency_key
required
string [ 1 .. 255 ] characters .*\S.*

Use a non-blank random value minted once for this create request so retries do not create duplicate webhook endpoints.

url
required
string <uri>
enabled_events
Array of strings non-empty

Event types to deliver. Omit or use ["*"] for all. Event type values are open and opaque.

description
string or null

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "id": "we_01j1n5qyj7pq43fvg4c142h9f6",
  • "enabled_events": [
    ],
  • "status": "enabled",
  • "secret": "string",
  • "description": "string",
  • "created_at": "2026-07-31T09:00:00Z"
}

List webhook endpoints

Required scope: webhooks:read.

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque cursor (next_cursor from a prior page). Re-supply the same filters; a mismatch, expired cursor, or invalid cursor returns 400.

limit
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "has_more": true,
  • "next_cursor": "string",
  • "data": [
    ]
}

Retrieve a webhook endpoint

Required scope: webhooks:read.

Authorizations:
bearerAuth
path Parameters
endpoint_id
required
string
Example: we_01j1n5qyj7pq43fvg4c142h9f6

Responses

Response samples

Content type
application/json
{
  • "id": "we_01j1n5qyj7pq43fvg4c142h9f6",
  • "enabled_events": [
    ],
  • "status": "enabled",
  • "secret": "string",
  • "description": "string",
  • "created_at": "2026-07-31T09:00:00Z"
}

Update a webhook endpoint

Required scope: webhooks:write.

Update url, enabled_events, or status.

  • Omitted fields remain unchanged.
  • A supplied field replaces its previous value.
  • enabled_events is replaced as a whole, not merged, and cannot be empty.
  • Set status to disabled to pause delivery without removing the endpoint. Set it back to enabled to resume delivery.

Supply at least one field to update. An empty body or idempotency_key alone returns 400 invalid_request.

Authorizations:
bearerAuth
path Parameters
endpoint_id
required
string
Example: we_01j1n5qyj7pq43fvg4c142h9f6
Request Body schema: application/json
required
Any of
url
required
string <uri>

Replacement delivery URL.

idempotency_key
string [ 1 .. 255 ] characters .*\S.*

Optional. When supplied, it must not be blank. Makes this update safe to retry; reusing the same key with a different request fingerprint returns 409 (see Idempotency).

enabled_events
Array of strings non-empty

Replacement event selection. Cannot be empty; use ["*"] for all events.

status
string
Enum: "enabled" "disabled"

Set disabled to pause delivery without removing the endpoint; set enabled to resume.

Responses

Request samples

Content type
application/json
{
  • "status": "enabled",
  • "idempotency_key": "string",
  • "enabled_events": [
    ]
}

Response samples

Content type
application/json
{
  • "id": "we_01j1n5qyj7pq43fvg4c142h9f6",
  • "enabled_events": [
    ],
  • "status": "enabled",
  • "secret": "string",
  • "description": "string",
  • "created_at": "2026-07-31T09:00:00Z"
}

Delete a webhook endpoint

Required scope: webhooks:write.

Permanently remove the endpoint and stop delivery. To pause delivery without removing its configuration or signing secret, update its status to disabled instead.

Recreating a deleted endpoint creates a new endpoint with a new signing secret.

Authorizations:
bearerAuth
path Parameters
endpoint_id
required
string
Example: we_01j1n5qyj7pq43fvg4c142h9f6

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "destination.account_id has an unexpected prefix",
  • "param": "destination.account_id",
  • "request_id": "req_01jj0m9w2k3n5p7q9r1s3t5v7w"
}

Rotate a webhook signing secret

Required scope: webhooks:write.

Rotate the endpoint's signing secret.

  • The new secret appears only in the original successful response.
  • An idempotent replay returns secret: null.
  • If the original response is lost, rotate again with a new key.
Authorizations:
bearerAuth
path Parameters
endpoint_id
required
string
Example: we_01j1n5qyj7pq43fvg4c142h9f6
Request Body schema: application/json
required
idempotency_key
required
string [ 1 .. 255 ] characters .*\S.*

Use a non-blank random value minted once for this rotation request so retries do not rotate the secret more than once.

Responses

Request samples

Content type
application/json
{
  • "idempotency_key": "c6d78b1f-83f1-4d54-8d8d-7a667865c3f7"
}

Response samples

Content type
application/json
{
  • "id": "we_01j1n5qyj7pq43fvg4c142h9f6",
  • "enabled_events": [
    ],
  • "status": "enabled",
  • "secret": "string",
  • "description": "string",
  • "created_at": "2026-07-31T09:00:00Z"
}

List events

Required scope: events:read.

List retained events across the account associated with the API key and its complete owned sub-account hierarchy.

  • Events remain available for 7 days from created_at.
  • Older events are omitted.
  • Date filters outside the retention window return only retained events.

When account_id is supplied, it selects one owned account's portfolio context.

Authorizations:
bearerAuth
query Parameters
account_id
string
Example: account_id=biz_01j8ma3wv3mepwabfxxs0yc395

Select the viewpoint of the account associated with the API key or one of its sub-accounts. Roster membership is not sufficient.

  • Malformed or wrong-prefix value: 400; param is account_id.
  • Well-formed non-owned account: 404.
  • Owned account with no list matches: empty page.
type
string (EventType)

Opaque event type identifier in resource.event form. This is an open string; new values may appear additively.

subject_id
string

Only events whose subject.id equals this value.

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).

cursor
string

Opaque cursor (next_cursor from a prior page). Re-supply the same filters; a mismatch, expired cursor, or invalid cursor returns 400.

limit
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "has_more": true,
  • "next_cursor": "string",
  • "data": [
    ]
}

Retrieve an event

Required scope: events:read.

Retrieve a retained event. Events expire 7 days after created_at; an expired event returns 404.

Authorizations:
bearerAuth
path Parameters
event_id
required
string
Example: evt_01j56eb11k4xtsrjhy22xtbdqx

Responses

Response samples

Content type
application/json
{
  • "id": "evt_01j56eb11k4xtsrjhy22xtbdqx",
  • "type": "string",
  • "account_id": "biz_01j8ma3wv3mepwabfxxs0yc395",
  • "subject": {
    },
  • "related": [
    ],
  • "created_at": "2026-07-31T09:00:00Z"
}

Replay an event

Required scope: events:write.

Re-deliver a retained event to all matching webhook endpoints. Manual replay is available only during the 7-day retention window; an expired event returns 404. This operation does not define automatic webhook retry or delivery guarantees.

Authorizations:
bearerAuth
path Parameters
event_id
required
string

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "destination.account_id has an unexpected prefix",
  • "param": "destination.account_id",
  • "request_id": "req_01jj0m9w2k3n5p7q9r1s3t5v7w"
}