# API Fundamentals Source: https://docs.primevault.com/api-basics/api-fundamentals Base URL, shared request conventions, example format, and a production checklist for the PrimeVault API. PrimeVault's external API is an organization-scoped JSON API. The rules below apply to every endpoint unless an endpoint page says otherwise. Request and response shapes match the current `@primevault/js-api-sdk`. ## Base URL ```text theme={null} https://api.primevault.com ``` All paths are relative to this URL. Requests and responses are JSON unless noted. ## Start here > [card] [Authentication](/api-basics/authentication) > > Credentials, request signing, headers, key safety, and auth troubleshooting. > [card] [Filtering and Pagination](/api-basics/filtering-and-pagination) > > List filters, `limit`, cursor pagination, and SDK iteration. > [card] [Status Codes](/api-basics/status-codes) > > HTTP status classes, SDK error types, and retry guidance. > [card] [Custom Error Codes](/api-basics/custom-error-codes) > > Error envelope, handling order, and endpoint-specific codes. ## Endpoint reference > [card] [Endpoints](https://docs.primevault.com/endpoints) > > Operations for assets and chains, vaults, transactions, contacts, and bank accounts. > [card] [Data Models](/api-basics/data-models) > > Shared request and response objects. Each operation lists its SDK method, REST path, parameters, request/response JSON, and validation or lifecycle notes. ## Shared request conventions | Convention | Rule | | :- | :- | | Property names | camelCase. SDK `chain` parameters map to `blockChain` where the REST API expects it. | | IDs | Opaque strings. Don't infer resource type, organization, or permissions from their format. | | Amounts | Decimal strings such as `"12.50"`. **Never JSON numbers** — floats lose precision. | | Optional fields | Omit if unused. Marked `?` in TypeScript types. | | Organization scope | Set by the API user. Never use resource IDs from another organization. | | Sub-orgs | Send `subOrgId` only where the request type defines it. On a party it identifies that party; at the top level it scopes the record. | | Timestamps | ISO 8601, UTC, unless noted. | | Unknown fields | Don't rely on undocumented response fields. Tolerate new ones without changing your requests. | ## JSON example convention * **POST and PUT** examples show the actual request body. * **GET** examples show path and query parameters as JSON for documentation only; they're encoded in the URL, not sent as a body. * **Responses** are representative and may omit optional fields. ## Integration checklist Complete these before going to production. **1. Create a dedicated API user** Keep its private key out of source control. See [Setting Up an API User](/api-user-setup/setting-up-api-user). **2. Verify authentication** Call a read-only endpoint with the official SDK. **3. Implement pagination** Use cursor traversal on every list endpoint you consume. **4. Handle errors in order** Typed HTTP errors first, then custom error codes. **5. Retry safely** When retrying a timed-out transaction creation, reuse a stable client reference where supported. **6. Log without secrets** Log request context, HTTP status, and error code — never credentials or sensitive payloads. # Authentication Source: https://docs.primevault.com/api-basics/authentication Authenticate PrimeVault API requests with an API-user key and a short-lived ES256 bearer token bound to the exact request path and JSON body. PrimeVault authenticates every external API request with an API-user key and a short-lived ES256 bearer token. Authentication is organization-scoped: credentials may access only the organization and permissions assigned to that API user. ## Base URL ```text theme={null} https://api.primevault.com ``` All endpoint paths are relative to this URL. ## Recommended: use the JavaScript SDK The SDK signs the exact request URL and JSON body, adds the required headers, and maps HTTP failures to typed errors. ```typescript theme={null} import { APIClient } from "@primevault/js-api-sdk"; const apiClient = new APIClient( process.env.PRIMEVAULT_API_KEY!, "https://api.primevault.com", process.env.PRIMEVAULT_ACCESS_PRIVATE_KEY!, ); ``` > [!WARNING] > > Create the client only in a trusted server environment. Never embed the API key or access private key in browser, mobile, or other distributed client code. ## Generate a bearer token manually Generate tokens manually only when you can't use the official SDK to send the request. The token is request-bound: generate it from the relative URL path and the same body object that will be sent, then create a new token for the next request. The examples below accept either a PEM private key or a hex-encoded PKCS#8 DER private key. They intentionally use the DER-encoded ECDSA signature produced by the PrimeVault SDKs. Do not replace the signing step with a generic JWT helper unless it can emit that signature format. ```javascript JavaScript (Node.js) expandable theme={null} import { createHash, createPrivateKey, createSign, randomUUID, } from "node:crypto"; const sortKeys = (value) => { if (Array.isArray(value)) return value.map(sortKeys); if (value && typeof value === "object") { return Object.fromEntries( Object.keys(value) .sort() .map((key) => [key, sortKeys(value[key])]), ); } return value; }; const base64url = (value) => Buffer.from(value).toString("base64url"); function generatePrimeVaultToken(apiKey, privateKey, urlPath, body = {}) { const now = Math.floor(Date.now() / 1000); const bodyHash = createHash("sha256") .update(JSON.stringify(sortKeys(body))) .digest("hex"); const header = { alg: "ES256", typ: "JWT" }; const payload = { iat: now, exp: now + 120, urlPath, userId: apiKey, body: bodyHash, jti: randomUUID(), }; const signingInput = [header, payload] .map((part) => base64url(JSON.stringify(sortKeys(part)))) .join("."); const trimmedKey = privateKey.trim(); const key = trimmedKey.startsWith("-----BEGIN") ? createPrivateKey(trimmedKey) : createPrivateKey({ key: Buffer.from(trimmedKey, "hex"), format: "der", type: "pkcs8", }); const signer = createSign("SHA256"); signer.update(signingInput); signer.end(); return `${signingInput}.${base64url(signer.sign(key))}`; } const apiKey = process.env.PRIMEVAULT_API_KEY; const privateKey = process.env.PRIMEVAULT_ACCESS_PRIVATE_KEY; const urlPath = "/api/external/vaults/"; const token = generatePrimeVaultToken(apiKey, privateKey, urlPath); const headers = { Authorization: `Bearer ${token}`, "Api-Key": apiKey, Accept: "application/json", }; ``` ```python Python expandable theme={null} import base64 import hashlib import json import os import time import uuid from typing import Optional from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec def base64url(value: bytes) -> str: return base64.urlsafe_b64encode(value).decode("ascii") def generate_primevault_token( api_key: str, private_key: str, url_path: str, body: Optional[dict] = None, ) -> str: now = int(time.time()) canonical_body = json.dumps( body or {}, sort_keys=True, separators=(",", ":"), ensure_ascii=False, ) header = {"alg": "ES256", "typ": "JWT"} payload = { "iat": now, "exp": now + 120, "urlPath": url_path, "userId": api_key, "body": hashlib.sha256(canonical_body.encode()).hexdigest(), "jti": str(uuid.uuid4()), } signing_input = ".".join( base64url( json.dumps( part, sort_keys=True, separators=(",", ":"), ensure_ascii=False, ).encode() ) for part in (header, payload) ) trimmed_key = private_key.strip() if trimmed_key.startswith("-----BEGIN"): key = serialization.load_pem_private_key( trimmed_key.encode(), password=None, ) else: key = serialization.load_der_private_key( bytes.fromhex(trimmed_key), password=None, ) signature = key.sign( signing_input.encode(), ec.ECDSA(hashes.SHA256()), ) return f"{signing_input}.{base64url(signature)}" api_key = os.environ["PRIMEVAULT_API_KEY"] private_key = os.environ["PRIMEVAULT_ACCESS_PRIVATE_KEY"] url_path = "/api/external/vaults/" token = generate_primevault_token(api_key, private_key, url_path) headers = { "Authorization": f"Bearer {token}", "Api-Key": api_key, "Accept": "application/json", } ``` For POST and PUT requests, construct the body once, pass that object to the token helper, and send the same object as JSON without mutating its keys or values between signing and transmission. ## Required headers | Header | Value | Purpose | | - | - | - | | `Authorization` | `Bearer ` | Short-lived ES256 token bound to the exact request path and, for writes, the exact JSON body. | | `Api-Key` | API-user key | Identifies the API user and organization. | | `version` | Installed SDK version | Identifies the client contract. Added by the SDK. | | `Content-Type` | `application/json` | Required when a JSON request body is sent. | | `Accept` | `application/json` | Requests a JSON response. Added by the SDK. | Header names are case-insensitive at the HTTP layer, but use the spelling above in examples and diagnostics. ## How request signing works **Step 1** Build the relative request URL, excluding the scheme and host. The query string may be included; the verifier compares the path component. **Step 2** Canonically serialize the request body with recursively sorted keys and compact JSON separators. Use an empty object for a request without a body. **Step 3** Hash that canonical body with SHA-256 and create a short-lived payload containing `iat`, `exp`, `urlPath`, `userId`, `body`, and a unique `jti`. **Step 4** Base64url-encode the header and payload, then sign `header.payload` with P-256 and SHA-256. **Step 5** Send the resulting bearer token with the same API-user key in the `Api-Key` header. > [!NOTE] > > Generate a new token for every request. Changing the path, body keys, or body values after token generation invalidates the request binding. ## Credential and key safety * Store credentials in a secret manager or protected server environment variables. * Do not log bearer tokens, API keys, private keys, or complete bank-account details. * Rotate credentials immediately if they may have been exposed. * Keep server time synchronized. Tokens expire 120 seconds after they are issued, so clock skew causes 401 errors. * Restrict source IPs where appropriate; see [API user IP whitelisting](/api-user-setup/ip-whitelisting). * Use the least-privileged API user required by the integration. ## Authentication failures | HTTP status | Meaning | What to check | | - | - | - | | `401` | Authentication failed | Missing or expired token, wrong API key, mismatched path/body signature, or clock skew. | | `403` | Authenticated but not permitted | API-user role, policy, vault permission, organization scope, sub-org scope, or IP allowlist. | > [!WARNING] > > Do not treat a 403 as a token-refresh problem. Verify authorization and resource scope before retrying. ## Setup references Use [Setting up API User](/api-user-setup/setting-up-api-user) to provision credentials and [API user IP whitelisting](/api-user-setup/ip-whitelisting) to restrict allowed source addresses. # Custom Error Codes Source: https://docs.primevault.com/api-basics/custom-error-codes Read PrimeVault API error responses: the message and custom code fields, the order to handle errors in, transaction error codes, and safe retries. PrimeVault error responses combine an HTTP status with a human-readable message and, for known failures, a machine-readable custom code. The HTTP status remains authoritative for the broad failure class; the custom code enables precise handling inside that class. ## Error response format ```json theme={null} { "message": "Vault has insufficient balance to complete the transaction", "code": "4003" } ``` | Field | Required | Description | | - | - | - | | `message` | Yes | Human-readable diagnostic text. Suitable for logs and operator-facing messages after sensitive values are removed. | | `code` | No | Stable machine-readable PrimeVault error code for a known failure. Generic validation failures can use `"400"`, and a few low-level failures may omit it. | > [!TIP] > > Branch program logic on `code` when a documented code exists. Use `message` as diagnostic context, not as a stable identifier. ## Handling order **Step 1** Inspect the HTTP status and typed SDK error class. **Step 2** Read the custom `errorCode` when present. **Step 3** Apply handling for that documented code. **Step 4** Fall back to the HTTP status and message when no custom code is available. **Step 5** Preserve the original status and code in structured logs, without logging credentials or sensitive request data. ## SDK examples > [tab] TypeScript / JavaScript > > The SDK error carries `message`, `errorCode`, `status`, and `responseText`. > > ```typescript theme={null} > import { > BadRequestError, > TooManyRequestsError, > } from "@primevault/js-api-sdk"; > > try { > await apiClient.createTransferTransaction({ > source, > destination, > amount, > asset, > chain, > }); > } catch (error) { > if (error instanceof BadRequestError) { > switch (error.errorCode) { > case "4003": > // Insufficient balance. > break; > case "4011": > // Compliance or OFAC restriction. > break; > case "4013": > // Invalid transaction parameters. > break; > default: > console.error(error.status, error.message); > } > } else if (error instanceof TooManyRequestsError) { > // Retry with bounded exponential backoff and jitter. > } > } > ``` > [tab] Python > > The Python SDK error exposes `response_text` and `code`. > > ```python theme={null} > from primevault_python_sdk.base_api_client import BadRequestError > > try: > api_client.create_transfer_transaction(request) > except BadRequestError as error: > if error.code == "4003": > # Insufficient balance. > ... > else: > # Fall back to the HTTP class and diagnostic message. > print(error.response_text) > ``` ## Retry safety * Do not retry a known validation, permission, compliance, or insufficient-balance error unchanged. * Retry `429` and transient `5xx` responses only with bounded backoff. See [Status Codes](/api-basics/status-codes) for retry guidance by status. ## Transaction error codes Transaction endpoints return the following custom codes, typically with a 400 status. Use the Safe to retry? column to decide whether to fix the request, wait for an external change, or stop. | Code | Name | When it happens | Example message | Safe to retry? | | - | - | - | - | - | | `4001` | `ON_CHAIN_VALIDATION_ERROR` | The chain/RPC rejected the built transaction | RPC error text | Yes — fix input, resend | | `4002` | `TXN_MESSAGE_VALIDATION_ERROR` | Invalid EIP-712 / typed data (contract call, WalletConnect) | validation text | Yes | | `4003` | `TOKEN_BALANCE_LOW` | Insufficient asset balance in the vault | Vault has insufficient balance to complete the transaction | Yes — after funding | | `4004` | `GAS_TOKEN_BALANCE_LOW` | Insufficient native token for gas (source or fee-payer vault) | Vault has insufficient native token balance to complete the transaction | Yes — after funding | | `4005` | `DESTINATION_ERROR` | Missing/invalid contact, vault, or address; bad address format | `Invalid destination address: for chain: ` | Yes — after fixing | | `4006` | `POLICY_CONFLICT` | Blocked by policy / no matching / ambiguous policy | Transaction blocked by policy. | Yes — until policy changes | | `4011` | `COMPLIANCE_BLOCKED` | OFAC / sanctioned destination address | Cannot add OFAC sanctioned address | No — change the destination | | `4012` | `CHAIN_NOT_ENABLED` | The chain is not enabled for your organization | ` transactions are not enabled at this time` | Yes — after enabling the chain | | `4013` | `INVALID_TXN_PARAMS` | Invalid category, unsupported gas parameter, or missing field | Invalid transaction category, should be TRANSFER, SWAP or CONTRACT\_CALL | Yes — fix the request parameters | ### Safe retries with externalId Send a stable, unique `externalId` for each logical payment and reuse the **same** value on every retry of that payment. `externalId` is unique per customer, so: * If the transaction was never created, the retry creates it. * If it was already created, the retry is safely rejected as a duplicate (A record with the same information already exists), so a second payment is impossible. # Data Models Source: https://docs.primevault.com/api-basics/data-models TypeScript data models used by the PrimeVault API and JavaScript SDK, including vaults, contacts, bank accounts, transactions, intents, quotes, and webhooks. These models mirror the public types and serializers used by the current `@primevault/js-api-sdk`. Optional properties are marked with `?`. Unless explicitly stated otherwise, IDs and decimal amounts are strings. ## Models on this page | Model | What it represents | Used by | | - | - | - | | [Cursor list response](#cursor-list-response) | Paginated envelope for list results | `getTransactions()`, `getVaults()`, `getContacts()`, `getBankAccounts()`, `getSubOrgs()` | | [Transfer parties](#transfer-parties) | The sender or receiver of a transfer | Transaction `source` and `destination`, intents | | [Asset and chain](#asset-and-chain) | An asset and the blockchain it lives on | Assets and chains | | [Vaults](#vaults) | An account that holds assets | `getVaults()` | | [Contacts](#contacts) | A saved blockchain address | `getContacts()` | | [Bank accounts](#bank-accounts) | A saved bank account | `getBankAccounts()` | | [Transactions](#transactions) | A transfer, swap, or other on-chain or fiat operation | `getTransactions()`, webhook events | | [Transaction operations](#transaction-operations-and-balance-changes) | One leg of a transaction workflow and its balance changes | Transaction `operations`, webhook events | | [Intent and quote](#intent-and-quote) | A payment order and its priced quote | `getQuote()`, `createTransactionFromIntent()` | | [Deposit instructions](#deposit-instructions) | Details for funding a vault or transaction | Vault and transaction deposit instructions | | [Fees and balances](#fees-and-balances) | Fee estimates and vault balances | Fee estimates, balances | | [Approval models](#approval-models) | Approval messages and approve/reject results | Approvals | | [Webhook event envelope](#webhook-event-envelope) | The payload wrapper for webhook events | Webhooks | ## Cursor list response Transaction, vault, contact, and bank-account list methods return the same cursor envelope. See [Cursor response](/api-basics/filtering-and-pagination#cursor-response) on Filtering and Pagination for its fields and an example. ## Transfer parties | Party type | Identification | | - | - | | `VAULT`, `CONTACT`, `BANK_ACCOUNT` | Use the organization-scoped resource `id`. | | `EXTERNAL_ADDRESS` | Use `address` and `chain`; do not put the address in `id`. | | `EXTERNAL_BANK_ACCOUNT` | Use the applicable `bankDetails` and payment-rail fields. | A party is the sender or receiver. Its `id` identifies that endpoint; `input.vaultId` and `output.vaultId` select the fiat accounts. Use IDs from your organization. Parties do not accept `subOrgId`. ### TransferPartyType ```typescript expandable theme={null} enum TransferPartyType { CONTACT = "CONTACT", VAULT = "VAULT", EXTERNAL_ADDRESS = "EXTERNAL_ADDRESS", EXTERNAL_BANK_ACCOUNT = "EXTERNAL_BANK_ACCOUNT", BANK_ACCOUNT = "BANK_ACCOUNT", } ``` ### TransferPartyData ```typescript expandable theme={null} interface TransferPartyData { type: TransferPartyType | string; id?: string; name?: string; address?: string; chain?: string; paymentRail?: string; } ``` ### BankDetails ```typescript expandable theme={null} interface BankDetails { accountName?: string; bankAccountInfo?: BankAccountInfo; beneficiary?: DepositInstructionBeneficiary; } ``` ### DepositInstructionBeneficiary Holder of the receiving bank account in [deposit instructions](#deposit-instructions). Every `address` field is optional. See [BankAccountBeneficiaryAddress](#bankaccountbeneficiary). ```typescript expandable theme={null} interface DepositInstructionBeneficiary { name?: string; address?: Partial; } ``` ## Asset and chain `details` is chain-specific. Use `symbol` plus `blockChain` to identify an asset representation. ### Asset ```typescript theme={null} interface Asset { name: string; symbol: string; blockChain: string; logoURL?: string; details?: unknown; } ``` ### ChainData ```typescript theme={null} interface ChainData { value: string; label: string; } ``` ## Vaults A vault is like an account. `asset: "NGN"` means it holds NGN. Set `input.vaultId` or `output.vaultId` to that account's ID. A null or missing `asset` does not identify a currency account. `walletsGenerated` describes blockchain-wallet readiness after creation and approval; use returned bank deposit instructions for fiat funding. ### VaultType ```typescript expandable theme={null} enum VaultType { EXCHANGE = "EXCHANGE", DEFAULT = "DEFAULT", GAS = "GAS", } ``` ### Vault ```typescript expandable theme={null} interface Vault { id: string; orgId: string; subOrgId?: string; vaultName: string; vaultType: VaultType; asset?: string | null; wallets: Array<{ id: string; blockchain: string; address?: string; publicKey?: string; }>; walletsGenerated: boolean; createdAt: string; updatedAt: string; isDeleted: boolean; } ``` ## Contacts ### ContactStatus ```typescript expandable theme={null} enum ContactStatus { PENDING = "PENDING", APPROVED = "APPROVED", DECLINED = "DECLINED", } ``` ### Contact ```typescript expandable theme={null} interface Contact { id: string; orgId: string; subOrgId?: string; name: string; blockChain: string; address: string; status: ContactStatus; isSmartContractAddress: boolean; tags?: string[]; createdById: string; isSanctioned: boolean; externalId?: string; createdAt: string; updatedAt: string; isDeleted: boolean; assetList?: string[]; } ``` ## Bank accounts > [!WARNING] > > Bank-account fields can contain sensitive financial data and beneficiary identity documents. Avoid including them in application logs or analytics payloads. ### BankAccountStatus ```typescript expandable theme={null} enum BankAccountStatus { PENDING = "PENDING", APPROVED = "APPROVED", DECLINED = "DECLINED", } ``` ### PaymentRail Payment rails the SDK defines. Use these values in `paymentRails` on a bank account. ```typescript expandable theme={null} enum PaymentRail { ACH = "ACH", WIRE = "WIRE", SEPA = "SEPA", SWIFT = "SWIFT", FPS = "FPS", NIP = "NIP", PIX = "PIX", UAEFTS = "UAEFTS", } ``` ### BankAccountInfo Bank and account identifiers. The fields a bank needs depend on the country and payment rail. ```typescript expandable theme={null} interface BankAccountInfo { country?: string | null; accountNumber?: string | null; routingNumber?: string | null; bankName?: string | null; bankAddress?: string | null; pixKey?: string | null; pixKeyType?: string | null; iban?: string | null; bic?: string | null; accountType?: "CHECKING" | "SAVINGS" | null; } ``` ### BankAccountBeneficiary The account holder. `entityType` selects the business or individual shape. ```typescript expandable theme={null} interface BankAccountBeneficiaryAddress { streetLine1: string; streetLine2?: string | null; city: string; state?: string | null; zip: string; country: string; } interface BusinessBeneficiaryIdentification { type: "COMPANY_REGISTRATION" | "LEI" | "TAX_ID"; value: string; issuingCountry?: string | null; } interface IndividualBeneficiaryIdentification { type: "PASSPORT" | "NATIONAL_ID" | "TAX_ID"; value: string; issuingCountry?: string | null; } // Shared fields. The SDK does not export this interface. interface BankAccountBeneficiaryDetails { name?: string | null; address: BankAccountBeneficiaryAddress; email?: string | null; phoneNumber?: string | null; } interface BusinessBankAccountBeneficiary extends BankAccountBeneficiaryDetails { entityType: "BUSINESS"; business: { legalName: string; registrationCountry?: string | null; }; identifications?: BusinessBeneficiaryIdentification[]; } interface IndividualBankAccountBeneficiary extends BankAccountBeneficiaryDetails { entityType: "INDIVIDUAL"; individual: { firstName: string; middleName?: string | null; lastName: string; dateOfBirth?: string | null; nationality?: string | null; gender?: string | null; occupation?: string | null; }; identifications?: IndividualBeneficiaryIdentification[]; } type BankAccountBeneficiary = | BusinessBankAccountBeneficiary | IndividualBankAccountBeneficiary; ``` ### BankAccount ```typescript expandable theme={null} interface BankAccount { id: string; orgId: string; subOrgId?: string; orgEntityId: string; createdById: string; createdAt: string; updatedAt: string; isDeleted: boolean; status: BankAccountStatus; accountName: string | null; bankAccountInfo?: BankAccountInfo | null; paymentRails: string[]; beneficiary?: BankAccountBeneficiary | null; currency: string | null; externalId: string | null; tags?: string[]; } ``` ## Transactions ### Transaction ```typescript expandable theme={null} interface Transaction { id: string; orgId: string; vaultId: string; status: TransactionStatus | string; transactionType: TransactionType | string; category: TransactionCategory | string; subCategory: TransactionSubCategory | string; createdAt: string; updatedAt: string; isDeleted: boolean; amount: string; txHash?: string; error?: string; externalId?: string; createdById?: string; fees?: Fees; memo?: string; txnSignature?: string; txnSignatureData?: Record; output?: TransactionOutput; amountInUSD?: string; nonce?: number; dAppId?: string; source?: TransferPartyData; destination?: TransferPartyData; quoteResponse?: Partial; depositInstructions?: DepositInstructions; operations?: TransactionOperation[]; balanceChanges?: TransactionOperationBalanceChanges | null; blockChain?: string; asset?: string; /** Deprecated: use destination.address. */ toAddress?: string; /** Deprecated: use destination.name. */ toAddressName?: string; /** Deprecated: use destination.id. */ toVaultId?: string; /** Deprecated: use source.address. */ sourceAddress?: string; } ``` ### TransactionType ```typescript expandable theme={null} enum TransactionType { INCOMING = "INCOMING", OUTGOING = "OUTGOING", } ``` ### TransactionCategory ```typescript expandable theme={null} enum TransactionCategory { TRANSFER = "TRANSFER", SWAP = "SWAP", TOKEN_TRANSFER = "TOKEN_TRANSFER", TOKEN_APPROVAL = "TOKEN_APPROVAL", CONTRACT_CALL = "CONTRACT_CALL", STAKE = "STAKE", REVOKE_TOKEN_ALLOWANCE = "REVOKE_TOKEN_ALLOWANCE", RAMP = "RAMP", FX = "FX", DELEGATE_RESOURCE = "DELEGATE_RESOURCE", } ``` ### TransactionSubCategory ```typescript expandable theme={null} enum TransactionSubCategory { INCOMING_TRANSFER = "INCOMING_TRANSFER", EXTERNAL_TRANSFER = "EXTERNAL_TRANSFER", INTERNAL_TRANSFER = "INTERNAL_TRANSFER", MARKET_TRADE = "MARKET_TRADE", APPROVE_TOKEN_ALLOWANCE = "APPROVE_TOKEN_ALLOWANCE", CUSTOM_MESSAGE = "CUSTOM_MESSAGE", CONTRACT_CALL = "CONTRACT_CALL", STAKE = "STAKE", UNSTAKE = "UNSTAKE", CLAIM = "CLAIM", DEPOSIT = "DEPOSIT", TRADE = "TRADE", WITHDRAW = "WITHDRAW", DEPOSIT_TRADE = "DEPOSIT_TRADE", TRADE_WITHDRAW = "TRADE_WITHDRAW", DEPOSIT_TRADE_WITHDRAW = "DEPOSIT_TRADE_WITHDRAW", ON_RAMP = "ON_RAMP", OFF_RAMP = "OFF_RAMP", } ``` ### TransactionStatus ```typescript expandable theme={null} enum TransactionStatus { DRAFT = "DRAFT", PENDING = "PENDING", APPROVED = "APPROVED", COMPLETED = "COMPLETED", FAILED = "FAILED", DECLINED = "DECLINED", SUBMITTED = "SUBMITTED", SIGNED = "SIGNED", WAITING_CONFIRMATION = "WAITING_CONFIRMATION", } ``` ## Transaction operations and balance changes Use operation `sequence` to preserve workflow order. Operation-level `balanceChanges` describes that leg; top-level transaction `balanceChanges` describes the aggregate transaction view when present. ### TransactionOperationType ```typescript expandable theme={null} enum TransactionOperationType { DEPOSIT = "DEPOSIT", TRADE = "TRADE", TRANSFER = "TRANSFER", WITHDRAW = "WITHDRAW", } ``` ### TransactionOperationStatus ```typescript expandable theme={null} enum TransactionOperationStatus { PENDING = "PENDING", PROCESSING = "PROCESSING", COMPLETED = "COMPLETED", FAILED = "FAILED", SKIPPED = "SKIPPED", CANCELLED = "CANCELLED", REVERSED = "REVERSED", } ``` ### TransactionOperationBalanceChange ```typescript expandable theme={null} interface TransactionOperationBalanceChange { party: TransferPartyData | null; asset: string; amount: string; chain?: string; paymentRail?: string; } ``` ### TransactionOperationBalanceChanges ```typescript expandable theme={null} interface TransactionOperationBalanceChanges { changes: TransactionOperationBalanceChange[]; } ``` ### TransactionOperation ```typescript expandable theme={null} interface TransactionOperation { source: TransferPartyData | null; destination: TransferPartyData | null; balanceChanges: TransactionOperationBalanceChanges | null; sequence: number; type: TransactionOperationType | string; status: TransactionOperationStatus | string; provider?: string; } ``` ## Intent and quote Think of an intent as a payment order: `input` is what you spend; `output` is what you receive. For a quote, supply both assets and exactly one amount, as a decimal string. Set `chain` or `paymentRail` on the source or destination. Get a quote with `getQuote({ intent })`, then execute its `quoteId` with `createTransactionFromIntent({ quoteId, externalId, memo })`. Quotes may include input and output amounts. Do not send `category`, `routeAccounts`, or `subOrgId`. Omitted fields stay omitted; quote-only execution sends `intent: null`. ### IntentAsset ```typescript expandable theme={null} interface IntentAsset { asset: string; amount?: string | null; vaultId?: string; } ``` ### TransactionIntentRequest ```typescript expandable theme={null} interface TransactionIntentRequest { input: IntentAsset; output: IntentAsset; source?: TransferPartyData; destination?: TransferPartyData; slippage?: string; } ``` ### GetQuoteRequest ```typescript expandable theme={null} interface GetQuoteRequest { intent: TransactionIntentRequest; } ``` ### TransactionExecuteIntentRequest ```typescript expandable theme={null} interface TransactionExecuteIntentRequest { intent?: TransactionIntentRequest | null; quoteId?: string | null; externalId?: string; memo?: string; } ``` ### Fees ```typescript expandable theme={null} interface Fees { amount: string; asset: string; amountInFiat?: string; } ``` ### QuoteResponseItem ```typescript expandable theme={null} interface QuoteResponseItem { quoteId: string; rate?: string | null; fees?: Fees | null; input?: IntentAsset; output?: IntentAsset; source?: TransferPartyData; destination?: TransferPartyData; slippage?: string | null; expiresAt?: string; } ``` ### QuoteResponse ```typescript expandable theme={null} interface QuoteResponse { quotes: QuoteResponseItem[]; } ``` ## Deposit instructions Deposit instructions are like bank transfer details. Request `asset` plus exactly one of `paymentRail` or `chain`. Vault instructions return a `results` list; transaction instructions return one optional object. Quotes do not include them. Use the returned details exactly. ### DepositInstructions ```typescript theme={null} interface DepositInstructions { type?: TransferPartyType | string; paymentRail?: string; bankDetails?: BankDetails; asset?: string; address?: string; chain?: string; memo?: string; } ``` ### GetVaultDepositInstructionsRequest ```typescript theme={null} type GetVaultDepositInstructionsRequest = { asset: string; } & ( | { chain: string; paymentRail?: never } | { chain?: never; paymentRail: string } ); ``` ### VaultDepositInstructionsResponse ```typescript theme={null} interface VaultDepositInstructionsResponse { results: DepositInstructions[]; } ``` ## Fees and balances ### FeeData ```typescript expandable theme={null} interface FeeData { expectedFeeInAsset: string; asset: string; expectedFeeInUSD: string; baseFee?: string; priorityFee?: string; } ``` ### EstimatedFeeResponse ```typescript expandable theme={null} interface EstimatedFeeResponse { high: FeeData; medium: FeeData; low: FeeData; } ``` ### BalanceResponse ```typescript expandable theme={null} interface BalanceResponse { [asset: string]: { [chain: string]: string; }; } ``` ### DetailedBalance ```typescript expandable theme={null} interface DetailedBalance { symbol: string; balance: string; name?: string; chain?: string; tokenAddress?: string; balanceInUSD?: string; price?: string; } ``` ### DetailedBalanceResponse ```typescript expandable theme={null} type DetailedBalanceResponse = DetailedBalance[]; ``` ## Approval models ### ApprovalAction ```typescript theme={null} enum ApprovalAction { APPROVE = "approve", REJECT = "reject", DECLINE = "reject", } ``` ### GetApprovalMessageResponse ```typescript theme={null} interface GetApprovalMessageResponse { approvalId: string; message: string; changeRequestId?: string; entityId?: string; } ``` ### ApprovalActionResponse ```typescript theme={null} interface ApprovalActionResponse { success: boolean; status?: string; id?: string; entityId?: string; } ``` ## Webhook event envelope ### WebhookEvent ```typescript theme={null} interface WebhookEvent { event: | "TRANSACTION_STATUS_CHANGED" | "TRANSACTION_OPERATION_STATUS_CHANGED"; version: "2.0.0"; eventId: string; data: { transaction?: Transaction; transactionOperation?: TransactionOperation; }; } ``` # Filtering and Pagination Source: https://docs.primevault.com/api-basics/filtering-and-pagination Page through PrimeVault list endpoints with opaque cursors and resource-specific query filters for transactions, vaults, contacts, and bank accounts. List endpoints use cursor pagination and resource-specific query filters. The same rules apply to transactions, vaults, contacts, and bank accounts. ## Supported list endpoints | Resource | Endpoint | JavaScript SDK | | - | - | - | | Transactions | `GET /api/external/transactions/` | `getTransactions()` | | Vaults | `GET /api/external/vaults/` | `getVaults()` | | Contacts | `GET /api/external/contacts/` | `getContacts()` | | Bank accounts | `GET /api/external/bank_accounts/` | `getBankAccounts()` | | Sub-orgs | `GET /api/external/sub_orgs/` | `getSubOrgs()` | ## Query parameters **limit** `number` Requested page size. The JavaScript SDK default is `20`. **cursor** `string | null` Opaque cursor returned by the preceding page. Omit it or send an empty value for the first page. **Resource filters** `string` Endpoint-specific filters supplied through `Record`. Filters are query-string values. Preserve the same filters and page size while following a cursor chain. ## First-page request The JSON below is a documentation representation of URL parameters; it is not sent as a request body. ```http HTTP theme={null} GET /api/external/vaults/?limit=20&cursor=&vaultName=Treasury ``` ```json Query parameters theme={null} { "query": { "limit": 20, "cursor": null, "vaultName": "Treasury" } } ``` ## Cursor response **results** `T[]` *required* Records on the current page. **nextCursor** `string | null` Opaque cursor to pass as `cursor` when requesting the next page. **hasNext** `boolean` `true` when another page is available. ```typescript Type theme={null} interface CursorListResponse { results: T[]; nextCursor?: string | null; hasNext?: boolean; } ``` ```json Example theme={null} { "results": [ { "id": "vault_123", "vaultName": "Treasury Vault" } ], "nextCursor": "eyJpZCI6InZhdWx0XzEyMyJ9", "hasNext": true } ``` > [!WARNING] > > Treat `nextCursor` as opaque. Do not decode it, edit it, construct it, or infer record ordering from it. ## JavaScript SDK example ```typescript theme={null} let cursor: string | null | undefined = null; do { const page = await apiClient.getVaults( { vaultName: "Treasury" }, 20, cursor, ); for (const vault of page.results) { console.log(vault.id, vault.vaultName); } cursor = page.hasNext ? page.nextCursor : null; } while (cursor); ``` ## Pagination rules **1. Request the first page** Start with a null or empty cursor. **2. Check for another page** Continue only when `hasNext === true` and `nextCursor` is present. **3. Request the next page** Reuse the preceding `nextCursor` exactly. Keep filters and page size stable across the cursor chain. **4. Stop** Stop when `hasNext` is not `true`. > [!WARNING] > > * Do not mix cursor responses with legacy page-number fields such as `count`, `next`, or `previous`. > * Restart from the first page when filters or sort inputs change. > * Do not assume an empty `results` array means there can never be another page; follow `hasNext`. ## Filtering rules * Send only filters documented by the resource page. * Encode every filter value as a string when using the SDK filter map. * Treat IDs as opaque and organization-scoped. * Never use a filter to bypass the API user's organization or sub-org permissions. * When a value is optional, omit the filter instead of sending the string `"undefined"` or `"null"`. # Status Codes Source: https://docs.primevault.com/api-basics/status-codes HTTP status codes returned by the PrimeVault API, the matching JavaScript SDK error classes, and retry guidance for each failure type. The PrimeVault API uses standard HTTP status codes to describe the transport-level outcome of each request. A successful write means the request was accepted or the resource was created; it does not mean an asynchronous vault or transaction lifecycle has completed. ## Success responses | Status | Meaning | Typical use | | - | - | - | | `200 OK` | Request completed successfully | Reads, updates, actions, quotes, and fee estimates. | | `201 Created` | Resource was created | Vault, contact, bank-account, and transaction creation. | > [!NOTE] > > For transactions and approval-backed resources, inspect the returned `status` field after a `200` or `201` response. ## Client and authorization errors | Status | Meaning | JavaScript SDK error | | - | - | - | | `400 Bad Request` | Invalid payload, unsupported state transition, or business-rule failure | `BadRequestError` | | `401 Unauthorized` | Missing, expired, or invalid request authentication | `UnauthorizedError` | | `403 Forbidden` | Authenticated API user lacks permission or resource scope | `ForbiddenError` | | `404 Not Found` | Resource does not exist in the request scope | `NotFoundError` | | `408 Request Timeout` | Server timed out waiting for the request | `RequestTimeoutError` | | `409 Conflict` | Request conflicts with current resource state | `ConflictError` | | `422 Unprocessable Entity` | Structured validation failed | `ValidationError` | | `429 Too Many Requests` | Organization or endpoint rate limit exceeded | `TooManyRequestsError` | ## Server and gateway errors | Status | Meaning | JavaScript SDK error | | - | - | - | | `500 Internal Server Error` | Unexpected server failure | `InternalServerError` | | `502 Bad Gateway` | Upstream gateway returned an invalid response | `BadGatewayError` | | `503 Service Unavailable` | Service is temporarily unavailable | `ServiceUnavailableError` | | `504 Gateway Timeout` | Upstream service timed out | `GatewayTimeoutError` | | Other unexpected status | Unmapped HTTP response | `UnknownError` | A network failure without an HTTP response is surfaced as `NetworkError`. ## Typed SDK error shape ```typescript theme={null} class BaseAPIException extends Error { message: string; errorCode?: string; status?: number; responseText?: unknown; } ``` ```typescript theme={null} import { NotFoundError } from "@primevault/js-api-sdk"; try { await apiClient.getVaultById("vault_123"); } catch (error) { if (error instanceof NotFoundError) { console.error(error.status, error.errorCode, error.message); } } ``` ## Retry guidance | Failure | Default behavior | | - | - | | `400`, `401`, `403`, `404`, `409`, `422` | Fix the request, credentials, permissions, or resource state before retrying. | | `408` or `429` | Retry with bounded exponential backoff and jitter. Respect `Retry-After` when present. | | `500`, `502`, `503`, `504` | Retry safe reads with bounded backoff. | | Network failure | Confirm whether the server received the request before repeating a write. | > [!WARNING] > > Do not blindly retry transaction or resource creation. Use a stable `externalId` where supported and retrieve the resulting resource before deciding whether to resubmit. ## HTTP status versus custom error code The HTTP status tells you the broad failure class. The JSON `code` identifies a specific PrimeVault business or validation failure when one is available. Handle both; see [Custom Error Codes](/api-basics/custom-error-codes). # Webhooks Source: https://docs.primevault.com/api-basics/webhooks Get notified when a PrimeVault transaction completes, fails, or is declined. Covers retries and delivery, deduplication, and signature verification. PrimeVault sends webhook notifications when a transaction reaches a terminal state, allowing your application to react to transaction outcomes without continuously polling the API. Configure one or more endpoint URLs in **Settings → Webhooks**. PrimeVault sends each event to every configured endpoint. ## Webhook event type ### TRANSACTION\_STATUS\_CHANGED Version 2.0.0 event sent when a transaction reaches `COMPLETED`, `FAILED`, or `DECLINED`. The current transaction is available in `data.transaction`, and `eventId` identifies the webhook delivery. ## Retries and delivery behavior PrimeVault sends each webhook as an HTTP `POST` request with a JSON body. Return a `2xx` response as soon as the event has been accepted, then perform longer processing asynchronously. * **Request timeout** — 30 seconds per attempt. If the endpoint does not respond in time, the attempt fails. * **Attempts** — Up to 4 total: one initial attempt plus up to three retries. * **Backoff** — 2 seconds before the second attempt, 4 seconds before the third, and 8 seconds before the fourth. * **Retry conditions** — Network errors, timeouts, and HTTP `4xx` or `5xx` responses are retried. * **After final failure** — Delivery stops after the fourth failed attempt. Reconcile transaction status through the [Transactions API](/transactions/overview) if delivery is missed. ## Handling recommendations Retries can produce duplicate deliveries, for example, when your endpoint processes a request but its response is lost. Make webhook handling idempotent and deduplicate using `eventId`. ## Verify webhook signatures Each delivery includes `X-Signature` and `X-Timestamp` headers. Use the verification key for the configured endpoint to verify the event before accepting or processing it. Compute HMAC-SHA256 over the canonical JSON body followed immediately by the `X-Timestamp` header value, with no separator. Canonical JSON sorts object keys, removes insignificant whitespace, and preserves Unicode characters. Compare the lowercase hexadecimal digest with `X-Signature` using a constant-time comparison. Reject missing or invalid signatures. Apply a timestamp tolerance appropriate for your receiver; the example below uses five minutes. Keep the receiver clock synchronized. PrimeVault generates a fresh timestamp and signature for each retry. ```python verify_webhook.py theme={null} import hashlib import hmac import json import time def verify_webhook( raw_body: bytes, timestamp: str, signature: str, verification_key: str, max_age_seconds: int = 300, ) -> bool: if ( len(signature) != 64 or not signature.isascii() or not timestamp.isascii() or not timestamp.isdecimal() ): return False try: if abs(int(time.time()) - int(timestamp)) > max_age_seconds: return False payload = json.loads(raw_body) canonical_body = json.dumps( payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False, ) except (ValueError, UnicodeDecodeError): return False expected = hmac.new( verification_key.encode("utf-8"), f"{canonical_body}{timestamp}".encode("utf-8"), hashlib.sha256, ).hexdigest() return hmac.compare_digest(expected, signature) ``` Pass the original request body bytes and the `X-Timestamp` and `X-Signature` header values into `verify_webhook`. Read missing headers as empty strings. Keep the endpoint verification key in your secret store. A valid signature authenticates the event; your handler must still validate the event version, type, and payload before applying changes. After verification, durably record or enqueue the event and return `2xx` promptly. Deduplicate retries by `eventId`. Make business updates idempotent by transaction ID and status as well, because a separate notification about the same transaction can have a new `eventId`. Use the Transactions API to reconcile current status. # Setting up API user for AWS Source: https://docs.primevault.com/api-user-setup/api-user-aws Keep your PrimeVault API user's private key in AWS KMS: create the signing key, let your EC2 instances or EKS Pods use it, and register its public key. When you run your services on AWS, you can keep the API user's private key in AWS Key Management Service (KMS). The private key never leaves KMS. Your EC2 instances or EKS Pods ask KMS to sign each request. The PrimeVault SDK manages the signing logic. ## Before you begin Make sure that you have: * An AWS account where you can create KMS keys, IAM policies, and IAM roles. * Your PrimeVault integration running on EC2 instances or on an EKS cluster. * Permission to create an API user in PrimeVault. ## Overview 1. [Create the KMS key](#create-the-kms-key) that holds the API user's private key. 2. [Create a signing policy](#create-the-signing-policy) that allows signing with that key only. 3. [Give your EC2 instances or EKS Pods the signing policy](#give-your-application-access-to-the-key). 4. [Register the public key and configure the SDK](#register-the-public-key-and-configure-the-sdk). > [!TIP] > > Create the KMS key in the same AWS region as your EC2 instances or EKS cluster. ## Create the KMS key **1. Configure the key** In the AWS console, go to **KMS** and select **Create key**. Select these settings: | Setting | Value | | - | - | | Key type | Asymmetric | | Key usage | Sign and verify | | Key spec | `ECC_NIST_P256` | ![KMS Configure key page with Asymmetric, Sign and verify, and ECC\_NIST\_P256 selected](https://mintcdn.com/prime-vault/9qQlbag4yPdZJggE/images/kms-configure-key.png?fit=max\&auto=format\&n=9qQlbag4yPdZJggE\&q=85\&s=7f1ab7695851499ccefb8d9b6ca8e9ec) **2. Add a label** Enter an alias for the key, for example `primevault-api-user-1`. The description is optional. ![KMS Add labels page with the alias primevault-api-user-1](https://mintcdn.com/prime-vault/9qQlbag4yPdZJggE/images/kms-add-labels.png?fit=max\&auto=format\&n=9qQlbag4yPdZJggE\&q=85\&s=fb219aaeb01975ab6ca94f62c8be2acf) **3. Define key administrators** Select the IAM users or roles that manage your KMS keys, for example your platform admin role. > [!WARNING] > > Do not select the role that your EC2 instances or EKS Pods use. Key administrators can change the key policy and give themselves more access. **4. Skip key usage permissions** Do not select any key users. Select **Next**. The signing policy in the next section gives your application access to this key. **5. Review and create the key** Review the settings, then select **Finish**. KMS shows a success message and lists the new key. ![KMS success message and the new key in the customer managed keys list](https://mintcdn.com/prime-vault/9qQlbag4yPdZJggE/images/kms-key-created.png?fit=max\&auto=format\&n=9qQlbag4yPdZJggE\&q=85\&s=dc87baa4e1bc5c2b9af549d7e589d62d) **6. Copy the key details** Open the key's detail page and copy these values: * **ARN:** you need it for the signing policy. * **Key ID:** save it as an environment variable. You need it to initialize the API client in your code. * **Public key:** open the **Public key** tab and copy the public key. * **Region:** note the AWS region of the key. ![KMS key detail page with the general configuration and the Public key tab](https://mintcdn.com/prime-vault/9qQlbag4yPdZJggE/images/kms-key-details-1.png?fit=max\&auto=format\&n=9qQlbag4yPdZJggE\&q=85\&s=7be47574c2a515de4c22ca1e1fa90719) ## Create the signing policy This IAM policy allows signing with your KMS key only. 1. In the AWS console, go to **IAM** > **Policies** > **Create policy** > **JSON**. 2. Paste the policy below. Replace the `Resource` value with the ARN of your KMS key. 3. Name the policy, for example `primevault-kms-sign`, then create it. ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Sid": "PrimeVaultApiUserSigning", "Effect": "Allow", "Action": [ "kms:Sign", "kms:GetPublicKey", "kms:DescribeKey" ], "Resource": "arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab" } ] } ``` ## Give your application access to the key Attach the signing policy to the IAM role that your application uses. Follow [Option 1](#option-1-ec2) for EC2 or [Option 2](#option-2-eks) for EKS. ### Option 1: EC2 Only EC2 instances with this role can sign with the key. > [!NOTE] > > If an IAM role is already attached to the EC2 instance, attach the `primevault-kms-sign` policy to that role. You don't need to do the steps below. You can see the attached role on the instance's detail page. **1. Create an IAM role** In the AWS console, go to **IAM** > **Roles** > **Create role**. For the trusted entity type, select **AWS service**. For the use case, select **EC2**. ![IAM Create role page with AWS service as the trusted entity type and EC2 as the use case](https://mintcdn.com/prime-vault/9qQlbag4yPdZJggE/images/ec2-create-role.png?fit=max\&auto=format\&n=9qQlbag4yPdZJggE\&q=85\&s=43365cbfc9658f9866db67601062e818) **2. Attach the signing policy** Search for `primevault-kms-sign` and select it. Name the role, for example `ec2-role`, then create it. **3. Assign the role to the EC2 instance** Open the EC2 instance's detail page. Select **Actions** > **Security** > **Modify IAM role**. ![EC2 instance Actions menu open at Security, Modify IAM role](https://mintcdn.com/prime-vault/9qQlbag4yPdZJggE/images/ec2-modify-iam-role-menu.png?fit=max\&auto=format\&n=9qQlbag4yPdZJggE\&q=85\&s=103848524705e5e75a2b066aa0574b97) Select the IAM role that you created, then select **Update IAM role**. ![Modify IAM role page with ec2-role selected](https://mintcdn.com/prime-vault/9qQlbag4yPdZJggE/images/ec2-update-iam-role.png?fit=max\&auto=format\&n=9qQlbag4yPdZJggE\&q=85\&s=7b28777905bf04e069c5adcd3acc2e40) > [!WARNING] > > If you run the PrimeVault application on more than one EC2 instance, assign the role to each instance. ### Option 2: EKS Your Pods get AWS permissions through IAM roles for service accounts. The AWS tutorial [IAM roles for service accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) shows how to associate an IAM role with a Kubernetes service account and configure your Pods to use it. Do these steps after your EKS cluster is set up. Follow the tutorial with the changes below. **1. Create an IAM OIDC provider for your cluster** Complete the steps in [Create an IAM OIDC provider for your cluster](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html). If you get a permission denied error, give the required permissions to the AWS user that sets up the cluster. Go to **IAM** > **Policies** > **Create policy** > **JSON**, and create a policy with the permissions below. ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "iam:GetOpenIDConnectProvider", "iam:ListOpenIDConnectProviders", "iam:CreateOpenIDConnectProvider", "iam:TagOpenIDConnectProvider", "eks:*", "iam:GetRole", "iam:ListAttachedRolePolicies", "iam:ListRolePolicies", "iam:ListRoles", "iam:PassRole" ], "Resource": "*" } ] } ``` If you still have issues, use the AWS Management Console option in the tutorial. **2. Assign an IAM role to a Kubernetes service account** Follow [Assign IAM roles to Kubernetes service accounts](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html) with these changes: * Skip step 1. You already created the signing policy. * In step 2, run this command: ```bash theme={null} eksctl create iamserviceaccount --name my-service-account --namespace default --cluster my-cluster --role-name my-role --attach-policy-arn arn:aws:iam::111122223333:policy/primevault-kms-sign --approve ``` Replace `111122223333` with your AWS account ID and `my-cluster` with the name of your cluster. Replace `my-role` and `my-service-account` with descriptive names, for example `eks-kms-access-role`. Validate the role's trust policy. Compare the output with the output in the tutorial: ```bash theme={null} aws iam get-role --role-name my-role --query Role.AssumeRolePolicyDocument ``` Check the attached policies: ```bash theme={null} aws iam list-attached-role-policies --role-name my-role --query AttachedPolicies ``` The output must include the ARN of the `primevault-kms-sign` policy. **3. Configure Pods to use the service account** Configure the Pods that run the PrimeVault SDK to use the service account from the previous step (`my-service-account` or the name you used). Follow the steps in [Configure Pods to use a Kubernetes service account](https://docs.aws.amazon.com/eks/latest/userguide/pod-configuration.html). **4. Check your AWS SDK version** Make sure your AWS SDK version is supported. See [Use IRSA with the AWS SDK](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-minimum-sdk.html). ## Register the public key and configure the SDK **1. Register the public key in PrimeVault** Add the KMS public key when you create the API user, then activate the user and save its API key. See [Setting up API user for production](/api-user-setup/api-user-production). **2. Configure the SDK** Set the signature service and the AWS region of the KMS key in the SDK config. Pass the KMS key ID when you create the API client: ```javascript theme={null} Config.set("SIGNATURE_SERVICE", "AWS_KMS") Config.set("AWS_REGION", "us-east-1") // the region of your KMS key const apiClient = new APIClient(apiKey, apiUrl, undefined, keyId) // keyId: the KMS key ID ``` The SDK gets AWS credentials from the EC2 instance role or the Pod's service account. You don't need AWS access keys in your code. The [JavaScript SDK repository](https://github.com/horcrux01/primevault_api_sdk_js_public?tab=readme-ov-file#option-2-aws_kms) has the full code example for setting up the API client. > [!WARNING] > > Never hardcode your API key in source code. Store it in an environment variable or a secrets manager. ## Related > [card] [Production setup](/api-user-setup/api-user-production) > > Register your public key, assign a role, and activate the API user. > [card] [Permissions](/api-user-setup/permissions-for-api-user) > > Understand Admin and User roles. # Setting up API user for production Source: https://docs.primevault.com/api-user-setup/api-user-production Set up a PrimeVault production API user: generate keys manually or with AWS KMS, register your public key, assign roles, and activate your API key. When you're ready to go live, create a dedicated production API user in PrimeVault. Follow these steps to generate and register your keys, assign the right role, and activate the user securely. ## Steps **1. Generate a public-private key pair** You have two secure options: > [card] [Generate manually](/api-user-setup/generate-key-pair) > > Use the SDK or OpenSSL to generate the key pair yourself. > [card] [Use AWS KMS](/api-user-setup/api-user-aws) > > Generate and manage your key pair with AWS Key Management Service (KMS). **2. Register your public key** Enter your public key in the PrimeVault UI when creating the API user. The private key must remain securely stored in your environment. You need it to initialize the API client in your code. **3. Assign a role** Choose **Admin** or **User**. To learn what each role can do, see [Permissions for API User](/api-user-setup/permissions-for-api-user). **4. Activate the API user** Approve the API user addition in the PrimeVault phone app. The API user then becomes active. **5. Save your API key** Copy the API key and save it as an environment variable. You need it to initialize the SDK or API client. **6. Clean up test users** Remove or deactivate any API users used only during testing. > [!WARNING] > > Never hardcode your API key or private key in source code. Store them in environment variables or a secrets manager. ## Related > [card] [Testing setup](/api-user-setup/api-user-testing) > > Try the flow with a dedicated test API user first. > [card] [Permissions](/api-user-setup/permissions-for-api-user) > > Understand Admin and User roles. # Setting up API user for testing Source: https://docs.primevault.com/api-user-setup/api-user-testing Create a dedicated PrimeVault test API user to safely try key generation, onboarding, and SDK integration before you move to production. Before connecting to PrimeVault's APIs in production, we strongly recommend creating a dedicated test API user. This lets you safely experiment with key generation, onboarding, and SDK integration, and confirm everything works as expected before you move to live deployments. > [!NOTE] > > Test organizations have these limits: > > * A maximum of 1,000 vaults > * A maximum of 10 workspace users > * A maximum of 100 transactions per day ## Steps **1. Generate a public-private key pair** Create the key pair for your test API user. For detailed steps, see [Generate Public-Private key pair for API User](/api-user-setup/generate-key-pair). **2. Register your public key** Enter the public key in PrimeVault's UI when creating a new API user. **3. Initialize the SDK** Use the private key to initialize the `APIClient` in your SDK. **4. Copy your API key** Once the API user is added and marked as **Active**, copy the generated API key from the UI and start using it in the SDK for testing. **5. Deactivate after testing** When testing is complete, deactivate the test API user to maintain security. > [!WARNING] > > Keep your private key secure, even for test users. It never leaves your environment and is never shared with PrimeVault. ## Next steps > [card] [Set up for production](/api-user-setup/api-user-production) > > Create an API user for live transactions. > [card] [Permissions](/api-user-setup/permissions-for-api-user) > > Control what your API user can do. # Generate Public-Private key pair for API User Source: https://docs.primevault.com/api-user-setup/generate-key-pair Generate the public-private key pair for your PrimeVault API user with the SDK's generatePublicPrivateKeyPair function or with OpenSSL. Public and private keys are cryptographic tools used to secure communication and authenticate users in digital systems. In the context of API access, these keys help ensure only authorized users can interact securely with your service. There are two recommended ways to generate your public-private key pair: using the SDK, or using OpenSSL from the command line. ## Option 1: Using the SDK **1. Set up the SDK** Make sure you have set up the SDK. **2. Generate the key pair** Call the `generatePublicPrivateKeyPair` util function in the SDK. **3. Save your keys** This will return your keys as strings. Save them securely. ## Option 2: Using OpenSSL (command line) **1. Install OpenSSL** Make sure OpenSSL is installed in your system. **2. Run the command** Run: ```bash theme={null} openssl ecparam -name prime256v1 -genkey -noout 2>/dev/null | tee >(openssl ec -pubout 2>/dev/null) ``` **3. Save your keys** This will output your private key and then your public key. Copy and save them securely. # Restrict API access with IP whitelisting Source: https://docs.primevault.com/api-user-setup/ip-whitelisting Use IP whitelisting to allow API requests only from trusted IP addresses. PrimeVault rejects any request that comes from an IP not on your list. Restrict API access to trusted IP addresses. When IP whitelisting is enabled, PrimeVault processes only requests that originate from the IPs you allow. ## How it works PrimeVault checks the source IP of every incoming API request and rejects any request from an IP that is not on your list. ## Add an IP address > [!WARNING] > > Add the IP addresses of your existing integrations before you rely on the whitelist. Requests from any IP not on the list are blocked. **1. Open IP Whitelist settings** Go to **Settings > IP Whitelist**. **2. Add the IP address** Enter the IP address you want to allow. **3. Save your changes** Save to apply the updated list. # Migrate to SDK V2 Source: https://docs.primevault.com/api-user-setup/migrate-to-sdk-v2 Update vault, contact, and transfer calls for @primevault/js-api-sdk V2. V2 adds groups and makes approval an explicit SDK call. SDK V2 changes how you create vaults, contacts, and transfers. Each create or update call opens a change request. Your API user must approve the change request before the change takes effect. This page lists the changes and shows the new call sequence. ## Install V2 > [!WARNING] > > The `latest` npm tag points to a 1.x release. A plain `npm install @primevault/js-api-sdk` installs V1. Pin the major version. ```bash theme={null} npm install @primevault/js-api-sdk@^2 ``` V2 requires Node.js 20 or later. Client setup does not change. See [Authentication](/api-basics/authentication). ## What changed | Area | Change in V2 | What to do | | :- | :- | :- | | Vaults | `templateId` is not required. | Remove `templateId` from the `createVault` payload. | | Vaults | A vault can join vault groups. | Pass `vaultGroupIds` in `createVault`. | | Vaults | Vault creation needs approval. | Call `createVaultApproval` after `createVault`. | | Contacts | A contact can join contact groups. | Pass `contactGroupIds` in `createContact` or `updateContact`. | | Contacts | Contact create and update need approval. | Call `createContactApproval` after `createContact` or `updateContact`. | | Transfers | A new transfer stays `PENDING` until approved. | Call `approveChangeRequest` with the transaction `id`. | ## How approval works Each approval call gets the approval message for the entity. The SDK signs the message with the key in your `APIClient`. The SDK then submits the signed approval. See [Approvals](/approvals/overview) for the REST flow. Use two separate calls: create, then approve. Store the entity `id` after the create call. If the approval call fails, retry the approval only. Do not repeat the create call, or you create a duplicate. ## Vaults **1. Create the vault** Pass `vaultName`, `chains`, and `vaultGroupIds`. Do not pass `templateId`. ```typescript theme={null} const vault = await apiClient.createVault({ vaultName: "Treasury Vault", chains: ["ETHEREUM", "POLYGON"], vaultGroupIds: ["vault_group_123"], }); ``` Copy the vault group IDs from the PrimeVault web app. The SDK has no method to list vault groups. **2. Approve the vault** ```typescript theme={null} await apiClient.createVaultApproval(vault); ``` `createVaultApproval` approves the change request and returns the vault. **3. Wait for wallet addresses** Wallet generation is asynchronous. Retrieve the vault until `walletsGenerated` is `true`. Then read the addresses from `wallets`. ```typescript theme={null} async function waitForWallets(vaultId: string, attempts = 30, delayMs = 2000) { for (let i = 0; i < attempts; i++) { const current = await apiClient.getVaultById(vaultId); if (current.walletsGenerated) return current; await new Promise((resolve) => setTimeout(resolve, delayMs)); } throw new Error(`Wallets not generated for vault ${vaultId}`); } const ready = await waitForWallets(vault.id); ready.wallets.forEach((w) => console.log(w.blockchain, w.address)); ``` The attempt count and delay are example values. Tune them for your workload. ## Contacts ### Create a contact ```typescript theme={null} const contact = await apiClient.createContact({ name: "Acme Payouts", address: "0x1111111111111111111111111111111111111111", chain: "ETHEREUM", assetList: ["USDC"], contactGroupIds: ["contact_group_123"], }); const approved = await apiClient.createContactApproval(contact); ``` A new contact has `status` set to `PENDING`. Use the contact in a transfer only after `status` is `APPROVED`. ### Update contact groups `updateContact` changes `assetList` and `contactGroupIds` only. It cannot change `name` or `address`. ```typescript theme={null} const current = await apiClient.getContactById("contact_123"); const updated = await apiClient.updateContact({ id: current.id, assetList: current.assetList ?? [], contactGroupIds: ["contact_group_123", "contact_group_456"], }); await apiClient.createContactApproval(updated); ``` > [!WARNING] > > `updateContact` sends `assetList: []` when you omit `assetList`. Always pass the current `assetList` of the contact to keep it. Pass the full list of group IDs that the contact must belong to. ## Transfers **1. Create the transfer** `source` and `destination` are party objects with a `type` and an `id`. `amount` is a decimal string. ```typescript theme={null} import { ApprovalAction, TransactionStatus } from "@primevault/js-api-sdk"; const txn = await apiClient.createTransferTransaction({ source: { type: "VAULT", id: "vault_123" }, destination: { type: "CONTACT", id: "contact_123" }, amount: "12.50", asset: "USDC", chain: "ETHEREUM", externalId: "payout_789", }); ``` **2. Approve the transfer** ```typescript theme={null} if (txn.status === TransactionStatus.PENDING) { await apiClient.approveChangeRequest({ entityId: txn.id, action: ApprovalAction.APPROVE, }); } ``` Your policy can approve a transfer on create. In that case the status is not `PENDING`. Skip the approval call. **3. Track the transfer** `approveChangeRequest` returns the approval result, not the transaction. Call `getTransactionById(txn.id)` or listen for [webhooks](/api-basics/webhooks). Stop when `status` is `COMPLETED`, `FAILED`, or `DECLINED`. ## One-call helpers V2 also has helpers that create and approve in one call. Use them in scripts and tests. In production, prefer two calls so a retry does not create a duplicate. | Two calls | One call | | :- | :- | | `createVault` + `createVaultApproval` | `createVaultWithApproval` | | `createContact` + `createContactApproval` | `createContactWithApproval` | | `updateContact` + `createContactApproval` | `updateContactWithApproval` | | `createTransferTransaction` + `approveChangeRequest` | `createTransactionWithApproval` | ## Other V1 methods that changed | V1 | V2 | | :- | :- | | `submitContactApprovalAction(entityId)` | `createContactApproval(contact)` | | `submitBankAccountApprovalAction(entityId)` | `createBankAccountApproval(bankAccount)` | | `ApprovalAction.DECLINE` sends `"decline"` | `ApprovalAction.REJECT` sends `"reject"`. `DECLINE` is an alias for `"reject"`. | | `getTradeQuote`, `getRampQuote` | `getQuote` | | `createTradeTransaction`, `createOnRampTransaction`, `createOffRampTransaction` | `createTransactionFromIntent` | | `updateBalances` | Removed. | | `isAutomation`, `executeAt` on transfer requests | Removed. | ## Next steps > [card] [Approvals](/approvals/overview) > > Approval messages, signing, and the REST approval flow. > [card] [Create a Transfer](/transactions/on-chain-operations/create-transfer) > > Transfer fields, fee payer, and lifecycle. # Permissions for API User Source: https://docs.primevault.com/api-user-setup/permissions-for-api-user Compare the Admin and User roles for PrimeVault API users, including vault access and creation rights, and follow least-privilege best practices. Every PrimeVault API user is assigned a role that controls what it can access and do within your organization. Choose **Admin** or **User** when you create the API user. ## Roles for API users > [card] Admin > > * Has read access to all vaults within the org by default. > * Can create vaults and manage contacts. > [card] User > > * Must be assigned to one or more vaults as a signer or viewer. > * Cannot create vaults directly. > * Ideal team members with specific operational roles. ## Best practices * Always assign the minimum privileges each API user needs. * Regularly review and update policy templates to align with org changes. > [!TIP] > > For most integrations, start with a User role assigned to only the vaults it needs, then expand access if required. ## Related > [card] [Testing setup](/api-user-setup/api-user-testing) > > Create a dedicated test API user. > [card] [Production setup](/api-user-setup/api-user-production) > > Create and activate a production API user. # Setting up API User Source: https://docs.primevault.com/api-user-setup/setting-up-api-user Set up a PrimeVault API user with a public-private key pair, store the private key securely, sign every request, and follow key management best practices. PrimeVault authenticates API users with a public-private key pair. You register the public key with PrimeVault, and the private key never leaves your secure environment (local secure storage, KMS, etc.). ## How it works **1. Generate a key pair** Create a public-private key pair using the SDK or OpenSSL. See [Generate Public-Private key pair for API User](/api-user-setup/generate-key-pair). **2. Register your public key** Register the public key with PrimeVault. The private key stays with you. **3. Secure your private key** Store the private key in a protected location. See the options below. **4. Sign your requests** Sign every API request with your private key. PrimeVault verifies all signatures to protect your account. ## Secure your private key Choose where your private key lives: > [card] [AWS Key Management Service (KMS)](/api-user-setup/api-user-aws) > > **Recommended.** Keys are generated and managed securely inside a hardware security module (HSM), prohibiting extraction. > [card] File or database > > Store the key in a protected file or database with restricted access. > [!WARNING] > > Never share your private key or commit it to source control. Anyone with your private key can sign requests on your behalf. ## Best practices * Use AWS KMS for the strongest security and compliance. * Rotate your keys regularly. * Configure KMS policies for least-privilege access. * Audit access logs and monitor for suspicious activity. ## Next steps > [card] [Set up for testing](/api-user-setup/api-user-testing) > > Create an API user for your test environment. > [card] [Set up for production](/api-user-setup/api-user-production) > > Create an API user for live transactions. > [card] [Set up for AWS](/api-user-setup/api-user-aws) > > Keep your private key in AWS KMS. > [card] [Permissions](/api-user-setup/permissions-for-api-user) > > Control what your API user can do. # Get an Approval Message Source: https://docs.primevault.com/approvals/get-approval-message GET https://api.primevault.com/api/external/change_requests/approvals/approval_message/ Get the message to sign for a change request waiting for approval: a transaction, vault, contact, or bank account. **GET** `/api/external/change_requests/approvals/approval_message/` Call this after any create or update call that needs approval: a transaction, a vault, a contact (create or update), or a bank account. Sign the returned `message` with your API user's private key, then send the signature to [Submit Approval Action](/approvals/submit-approval-action). See [Approvals](/approvals/overview) for the full flow. ## Query parameter **entityId** `string` *required* ID of the entity waiting for approval. Use the `id` returned by the create or update call: a transaction, vault, contact, or bank account ID. ## Response Returns a [GetApprovalMessageResponse](/api-basics/data-models#getapprovalmessageresponse). **approvalId** `string` Approval ID. Pass it to [Submit Approval Action](/approvals/submit-approval-action). **message** `string` Message to sign. Sign the exact string, unchanged. **changeRequestId** `string` Change request this approval belongs to. **entityId** `string` ID of the transaction, vault, contact, or bank account being approved. ```json Request example theme={null} { "query": { "entityId": "transaction_123" } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request GET \ --url "https://api.primevault.com/api/external/change_requests/approvals/approval_message/?entityId=transaction_123" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Accept: application/json" ``` ```typescript JavaScript SDK theme={null} // Pass a transaction, vault, contact, or bank account ID. const approvalMessage = await apiClient.getChangeApprovalMessage("transaction_123"); console.log(approvalMessage.approvalId, approvalMessage.message); ``` ```json Response example theme={null} { "approvalId": "approval_123", "message": "", "changeRequestId": "change_request_123", "entityId": "transaction_123" } ``` ```typescript JavaScript SDK theme={null} interface GetApprovalMessageResponse { approvalId: string; message: string; changeRequestId?: string; entityId?: string; } ``` # Approvals Source: https://docs.primevault.com/approvals/overview Transactions, vaults, contacts, and bank accounts you create or change through the API wait for your API user's approval. Get the approval message, sign it, and submit the action. Creating or changing certain resources through the API opens a change request. The change does not take effect until your API user approves it. Every change request uses the same two endpoints: [Get Approval Message](/approvals/get-approval-message) and [Submit Approval Action](/approvals/submit-approval-action). ## What needs approval For each change below, pass the `id` from the create or update response as `entityId`. | Change | Endpoint | SDK: create and approve | | - | - | - | | Create a transaction | Any transaction create call. See [Transactions](#transactions). | See [Transactions](#transactions). | | Create a vault | [Create a Vault](/vaults/create-vault) | `createVaultWithApproval()` | | Create a contact | [Create Contact](/blockchain-contacts/create-contact) | `createContactWithApproval()` | | Update a contact | [Update Contact](/blockchain-contacts/update-contact) | `updateContactWithApproval()` | | Create a bank account | [Create Bank Account](/bank-accounts/create-bank-account) | `createBankAccountWithApproval()` | ### Transactions A new transaction comes back with `status: "PENDING"`. If your policy approves on creation, it comes back in a later status and needs no action. | Flow | Create endpoint | SDK | | - | - | - | | Transfer | [Create Transfer](/transactions/on-chain-operations/create-transfer) | `createTransactionWithApproval()` creates and approves. `createTransferTransaction()` only creates. | | Contract call | [Create Contract Call](/transactions/on-chain-operations/create-contract-call) | Call `approveChangeRequest()` after create. | | Stake | [Stake Resource](/transactions/on-chain-operations/stake-resource) | Call `approveChangeRequest()` after create. | | Delegate | [Delegate Resource](/transactions/on-chain-operations/delegate-resource) | Call `approveChangeRequest()` after create. | | Replace | [Replace Transaction](/transactions/on-chain-operations/replace-transaction) | Call `approveChangeRequest()` after create. | | Ramp | [Execute a Ramp Quote](/transactions/ramps/execute-quote) | `createTransactionFromIntent()` approves for you. | | FX | [Execute an FX Quote](/transactions/fx/execute-quote) | `createTransactionFromIntent()` approves for you. | | Trade | [Execute a Trade Quote](/transactions/trade/execute-quote) | `createTransactionFromIntent()` approves for you. | ## Approval flow **1. Create or update the entity** Call the endpoint from [What needs approval](#what-needs-approval) and keep the `id` from the response. **2. Get the approval message** Call [Get Approval Message](/approvals/get-approval-message) with `entityId` set to that `id`. Save the `approvalId` and `message`. **3. Sign the message** Sign the exact `message` with your API user's private key, the same key that signs your requests (ECDSA with SHA-256), and hex-encode the signature. **4. Submit the action** Call [Submit Approval Action](/approvals/submit-approval-action) with the `approvalId`, `action: "approve"` (or `"reject"`), and the signature. **5. Confirm the result** Fetch the entity. Track a transaction with [Retrieve a Transaction](/transactions/retrieve-transaction) or [webhooks](/api-basics/webhooks) until it reaches `COMPLETED`, `FAILED`, or `DECLINED`. Check a vault with [Retrieve a Vault](/vaults/retrieve-vault). Check that a contact or bank account `status` moved from `PENDING` to `APPROVED` with [Retrieve Contact](/blockchain-contacts/retrieve-contact) or [Retrieve Bank Account](/bank-accounts/retrieve-bank-account). ## Approve with the SDK `approveChangeRequest()` gets the approval message, signs it with the client's configured key (a private key or AWS KMS), and submits the action. It works for any `entityId` in the table above. The `...WithApproval()` helpers call it for you. ```typescript theme={null} // Create and approve in one call const contact = await apiClient.createContactWithApproval(request); // Or create first, then approve const vault = await apiClient.createVault(request); await apiClient.approveChangeRequest({ entityId: vault.id, action: "approve", }); const approvedVault = await apiClient.getVaultById(vault.id); ``` > [card] [Get Approval Message](/approvals/get-approval-message) > > The message to sign for a pending change request. > [card] [Submit Approval Action](/approvals/submit-approval-action) > > Approve or reject with the signed message. # Submit an Approval Action Source: https://docs.primevault.com/approvals/submit-approval-action POST https://api.primevault.com/api/external/change_requests/approvals/{approvalId}/action/ Approve or reject a change request waiting for approval (a transaction, vault, contact, or bank account) with a signed approval message. **POST** `/api/external/change_requests/approvals/{approvalId}/action/` Send the signed approval message from [Get Approval Message](/approvals/get-approval-message) to approve or reject. See [Approvals](/approvals/overview) for the full flow. ## Path parameter **approvalId** `string` *required* The `approvalId` returned by [Get Approval Message](/approvals/get-approval-message). ## Request body **action** `ApprovalAction` *required* `approve` or `reject`. See [ApprovalAction](/api-basics/data-models#approvalaction). **signature** `string` *required* Hex-encoded ECDSA (SHA-256) signature of the approval `message`, made with your API user's private key. **reason** `string` Note recorded with the action. The SDK sends `ok` unless you pass another value. ## Response Returns an [ApprovalActionResponse](/api-basics/data-models#approvalactionresponse). **success** `boolean` Whether PrimeVault accepted the action. **status** `string` Approval status after the action. **id** `string` ID of the approval record. **entityId** `string` ID of the transaction, vault, contact, or bank account the action applied to. > [!NOTE] > > After you approve, fetch the entity to confirm its state: > > * **Transaction:** [Retrieve a Transaction](/transactions/retrieve-transaction) or [webhooks](/api-basics/webhooks) until it reaches `COMPLETED`, `FAILED`, or `DECLINED`. > * **Vault:** [Retrieve a Vault](/vaults/retrieve-vault). > * **Contact:** [Retrieve a Contact](/blockchain-contacts/retrieve-contact). Status moves from `PENDING` to `APPROVED` or `DECLINED`. > * **Bank account:** [Retrieve a Bank Account](/bank-accounts/retrieve-bank-account). Status moves from `PENDING` to `APPROVED` or `DECLINED`. ```json Request example theme={null} { "path": { "approvalId": "approval_123" }, "body": { "action": "approve", "signature": "", "reason": "ok" } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/change_requests/approvals/approval_123/action/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "action": "approve", "signature": "", "reason": "ok" }' ``` ```typescript JavaScript SDK theme={null} // Sign the message yourself and submit it: const result = await apiClient.submitChangeApprovalAction( approvalMessage.approvalId, "approve", signatureHex, ); // Or fetch, sign, and submit in one call. // entityId is a transaction, vault, contact, or bank account ID. await apiClient.approveChangeRequest({ entityId: transaction.id, action: "approve", }); ``` ```json Response example theme={null} { "success": true, "entityId": "transaction_123" } ``` ```typescript JavaScript SDK theme={null} interface ApprovalActionResponse { success: boolean; status?: string; id?: string; entityId?: string; } ``` # List Assets Source: https://docs.primevault.com/assets-and-chains/list-assets GET https://api.primevault.com/api/external/assets/ Retrieve every asset enabled for your organization, with its symbol, blockchain, and chain-specific details. **GET** `/api/external/assets/` Returns the organization's supported asset representations. The same symbol can appear more than once when it exists on multiple blockchains. ## Response Returns an array of [Asset](/api-basics/data-models#asset) objects. **name** `string` Asset name. **symbol** `string` Asset symbol, for example `USDC`. **blockChain** `string` Chain identifier. Use `symbol` with `blockChain` as the asset/network pair. **logoURL** `string` Logo image URL. **details** `object` Chain-specific metadata. Treat unrecognized keys as optional. ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request GET \ --url "https://api.primevault.com/api/external/assets/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Accept: application/json" ``` ```typescript JavaScript SDK theme={null} getAssetsData(): Promise ``` ```json Response example theme={null} [ { "name": "USD Coin", "symbol": "USDC", "blockChain": "ETHEREUM", "details": { "tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" } }, { "name": "USD Coin", "symbol": "USDC", "blockChain": "SOLANA", "details": { "tokenAddress": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" } } ] ``` ```typescript JavaScript SDK theme={null} // The response is an array: Asset[] interface Asset { name: string; symbol: string; blockChain: string; logoURL?: string; details?: unknown; } ``` # List Supported Chains Source: https://docs.primevault.com/assets-and-chains/list-supported-chains GET https://api.primevault.com/api/external/assets/supported_chains/ Retrieve the blockchain networks enabled for your organization, with the identifiers to use in other API calls. **GET** `/api/external/assets/supported_chains/` Returns the chain identifiers and display metadata enabled for the organization. ## Response Returns an array of [ChainData](/api-basics/data-models#chaindata) objects. **value** `string` Chain identifier. Use it wherever an endpoint expects a chain. **label** `string` Display name. ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request GET \ --url "https://api.primevault.com/api/external/assets/supported_chains/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Accept: application/json" ``` ```typescript JavaScript SDK theme={null} getSupportedChains(): Promise ``` ```json Response example theme={null} [ { "value": "ETHEREUM", "label": "Ethereum" }, { "value": "SOLANA", "label": "Solana" } ] ``` ```typescript JavaScript SDK theme={null} // The response is an array: ChainData[] interface ChainData { value: string; label: string; } ``` # Assets and Chains Source: https://docs.primevault.com/assets-and-chains/overview Find the assets and blockchain networks enabled for your organization. Use these read-only endpoints to discover the assets and chains currently enabled for the API user's organization. Do not hard-code the returned list. > [card] [List assets](/assets-and-chains/list-assets) > [card] [List supported chains](/assets-and-chains/list-supported-chains) # Create a Bank Account Source: https://docs.primevault.com/bank-accounts/create-bank-account POST https://api.primevault.com/api/external/bank_accounts/ Save a bank account. Required fields depend on the country and payment rail. The account needs your API user's approval. **POST** `/api/external/bank_accounts/` Creates a new fiat settlement or beneficiary bank account under the API user's organization. `accountName`, `bankAccountInfo`, `paymentRails`, and `currency` are required. The fields required inside `bankAccountInfo` and `beneficiary` depend on the country and payment rail. > [!NOTE] > > Send the complete set of `bankAccountInfo` and `beneficiary` fields for each rail in `paymentRails`. The SDK marks most nested fields optional, but the rail can still require them. ## Request body **subOrgId** `string` Sub-org that owns the account. Set only at creation. **accountName** `string` *required* Account-holder name. **bankAccountInfo** `BankAccountInfo` *required* Bank and account identifiers. Send the fields that the country and payment rail need. See [BankAccountInfo](/api-basics/data-models#bankaccountinfo). **Properties** **country** `string | null` Country code of the bank. **accountNumber** `string | null` Bank account number. **routingNumber** `string | null` Domestic routing identifier where applicable. **bankName** `string | null` Bank name. **bankAddress** `string | null` Bank address. **pixKey** `string | null` PIX key. Use with the `PIX` rail. **pixKeyType** `string | null` Type of the PIX key. **iban** `string | null` International Bank Account Number. **bic** `string | null` Bank Identifier Code (SWIFT code). **accountType** `string | null` `CHECKING` or `SAVINGS`. **paymentRails** `string[]` *required* Payment rails the account supports: `ACH`, `WIRE`, `SEPA`, `SWIFT`, `FPS`, `NIP`, `PIX`, or `UAEFTS`. See [PaymentRail](/api-basics/data-models#paymentrail). **beneficiary** `BankAccountBeneficiary` Account holder. Set `entityType`, then send the matching `business` or `individual` object. See [BankAccountBeneficiary](/api-basics/data-models#bankaccountbeneficiary). **Properties** **entityType** `string` *required* `BUSINESS` or `INDIVIDUAL`. **name** `string | null` Beneficiary name. **address** `BankAccountBeneficiaryAddress` *required* Beneficiary address. **Properties** **streetLine1** `string` *required* First street address line. **streetLine2** `string | null` Second street address line. **city** `string` *required* City. **state** `string | null` State or province. **zip** `string` *required* Postal code. **country** `string` *required* Country code. **email** `string | null` Beneficiary email address. **phoneNumber** `string | null` Beneficiary phone number. **business** `object` Required when `entityType` is `BUSINESS`. **Properties** **legalName** `string` *required* Registered legal name of the business. **registrationCountry** `string | null` Country where the business is registered. **individual** `object` Required when `entityType` is `INDIVIDUAL`. **Properties** **firstName** `string` *required* First name. **middleName** `string | null` Middle name. **lastName** `string` *required* Last name. **dateOfBirth** `string | null` Date of birth. **nationality** `string | null` Nationality. **gender** `string | null` Gender. **occupation** `string | null` Occupation. **identifications** `object[]` Identity documents of the beneficiary. **Properties** **type** `string` *required* `BUSINESS`: `COMPANY_REGISTRATION`, `LEI`, or `TAX_ID`. `INDIVIDUAL`: `PASSPORT`, `NATIONAL_ID`, or `TAX_ID`. **value** `string` *required* Document number or identifier. **issuingCountry** `string | null` Country that issued the document. **currency** `string` *required* Account currency, for example `USD`, `EUR`, `NGN`, or `AED`. **externalId** `string` Client-controlled bank-account reference. **tags** `string[]` Tags for the bank account. ## Response Returns the new [BankAccount](/api-basics/data-models#bankaccount) with `status: "PENDING"`. **id** `string` Bank account ID. **orgId** `string` Organization that owns the bank account. **subOrgId** `string` [Sub-org](/sub-orgs/overview) that owns the bank account, if any. **orgEntityId** `string` Organization entity that owns the account. **createdById** `string` User or API user that created the bank account. **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the bank account is deleted. **status** `BankAccountStatus` `PENDING`, `APPROVED`, or `DECLINED`. A new bank account stays `PENDING` until it's [approved](/approvals/overview). See [BankAccountStatus](/api-basics/data-models#bankaccountstatus). **accountName** `string | null` Account-holder name. **bankAccountInfo** `BankAccountInfo | null` Bank and account identifiers. See [BankAccountInfo](/api-basics/data-models#bankaccountinfo). **Properties** **country** `string | null` Country code of the bank. **accountNumber** `string | null` Bank account number. **routingNumber** `string | null` Domestic routing identifier where applicable. **bankName** `string | null` Bank name. **bankAddress** `string | null` Bank address. **pixKey** `string | null` PIX key. Used with the `PIX` rail. **pixKeyType** `string | null` Type of the PIX key. **iban** `string | null` International Bank Account Number. **bic** `string | null` Bank Identifier Code (SWIFT code). **accountType** `string | null` `CHECKING` or `SAVINGS`. **paymentRails** `string[]` Payment rails the account supports: `ACH`, `WIRE`, `SEPA`, `SWIFT`, `FPS`, `NIP`, `PIX`, or `UAEFTS`. See [PaymentRail](/api-basics/data-models#paymentrail). **beneficiary** `BankAccountBeneficiary | null` Account holder. `entityType` decides whether `business` or `individual` is present. See [BankAccountBeneficiary](/api-basics/data-models#bankaccountbeneficiary). **Properties** **entityType** `string` `BUSINESS` or `INDIVIDUAL`. **name** `string | null` Beneficiary name. **address** `BankAccountBeneficiaryAddress` Beneficiary address. **Properties** **streetLine1** `string` First street address line. **streetLine2** `string | null` Second street address line. **city** `string` City. **state** `string | null` State or province. **zip** `string` Postal code. **country** `string` Country code. **email** `string | null` Beneficiary email address. **phoneNumber** `string | null` Beneficiary phone number. **business** `object` Present when `entityType` is `BUSINESS`. **Properties** **legalName** `string` Registered legal name of the business. **registrationCountry** `string | null` Country where the business is registered. **individual** `object` Present when `entityType` is `INDIVIDUAL`. **Properties** **firstName** `string` First name. **middleName** `string | null` Middle name. **lastName** `string` Last name. **dateOfBirth** `string | null` Date of birth. **nationality** `string | null` Nationality. **gender** `string | null` Gender. **occupation** `string | null` Occupation. **identifications** `object[]` Identity documents of the beneficiary. **Properties** **type** `string` `BUSINESS`: `COMPANY_REGISTRATION`, `LEI`, or `TAX_ID`. `INDIVIDUAL`: `PASSPORT`, `NATIONAL_ID`, or `TAX_ID`. **value** `string` Document number or identifier. **issuingCountry** `string | null` Country that issued the document. **currency** `string | null` Account currency. **externalId** `string | null` Client-controlled bank-account reference. **tags** `string[]` Tags on the bank account. ## Approve the bank account A new bank account comes back with `status: "PENDING"` and needs your API user's approval. Call [Get Approval Message](/approvals/get-approval-message) with `entityId` set to the bank account `id`, sign the `message`, and send the signature to [Submit Approval Action](/approvals/submit-approval-action). See [Approvals](/approvals/overview) for the full flow. With the SDK, approve separately or in one call: ```typescript theme={null} const bankAccount = await apiClient.createBankAccount(request); const approved = await apiClient.createBankAccountApproval(bankAccount); // Convenience helper: create, then approve. const approvedBankAccount = await apiClient.createBankAccountWithApproval(request); ``` > [!NOTE] > > Use the returned `id` with [Retrieve Bank Account](/bank-accounts/retrieve-bank-account) to check the current `status`. ```json REST request body theme={null} { "subOrgId": "sub_org_123", "accountName": "Acme Corp", "bankAccountInfo": { "country": "US", "accountNumber": "111222333", "routingNumber": "021000021", "bankName": "Chase", "bankAddress": "270 Park Avenue, New York, NY 10017", "accountType": "CHECKING" }, "paymentRails": ["ACH", "WIRE"], "beneficiary": { "entityType": "BUSINESS", "name": "Acme Corp", "business": { "legalName": "Acme Corp", "registrationCountry": "US" }, "address": { "streetLine1": "100 Main Street", "streetLine2": "Suite 400", "city": "San Francisco", "state": "CA", "zip": "94105", "country": "US" }, "email": "treasury@acme.example", "identifications": [ { "type": "TAX_ID", "value": "12-3456789", "issuingCountry": "US" } ] }, "currency": "USD", "externalId": "erp-bank-001", "tags": ["payout"] } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/bank_accounts/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "subOrgId": "sub_org_123", "accountName": "Acme Corp", "bankAccountInfo": { "country": "US", "accountNumber": "111222333", "routingNumber": "021000021", "bankName": "Chase", "bankAddress": "270 Park Avenue, New York, NY 10017", "accountType": "CHECKING" }, "paymentRails": ["ACH", "WIRE"], "beneficiary": { "entityType": "BUSINESS", "name": "Acme Corp", "business": { "legalName": "Acme Corp", "registrationCountry": "US" }, "address": { "streetLine1": "100 Main Street", "streetLine2": "Suite 400", "city": "San Francisco", "state": "CA", "zip": "94105", "country": "US" }, "email": "treasury@acme.example", "identifications": [ { "type": "TAX_ID", "value": "12-3456789", "issuingCountry": "US" } ] }, "currency": "USD", "externalId": "erp-bank-001", "tags": ["payout"] }' ``` ```typescript JavaScript SDK theme={null} interface CreateBankAccountRequest { subOrgId?: string; accountName: string; bankAccountInfo: BankAccountInfo; paymentRails: string[]; beneficiary?: BankAccountBeneficiary; currency: string; externalId?: string; tags?: string[]; } createBankAccount(request: CreateBankAccountRequest): Promise ``` ```json Response example theme={null} { "id": "bank_account_123", "orgId": "org_456", "subOrgId": "sub_org_123", "orgEntityId": "org_entity_123", "createdById": "api_user_123", "createdAt": "2026-08-04T10:30:00Z", "updatedAt": "2026-08-04T10:30:00Z", "isDeleted": false, "status": "PENDING", "accountName": "Acme Corp", "bankAccountInfo": { "country": "US", "accountNumber": "111222333", "routingNumber": "021000021", "bankName": "Chase", "bankAddress": "270 Park Avenue, New York, NY 10017", "accountType": "CHECKING" }, "paymentRails": ["ACH", "WIRE"], "beneficiary": { "entityType": "BUSINESS", "name": "Acme Corp", "business": { "legalName": "Acme Corp", "registrationCountry": "US" }, "address": { "streetLine1": "100 Main Street", "streetLine2": "Suite 400", "city": "San Francisco", "state": "CA", "zip": "94105", "country": "US" }, "email": "treasury@acme.example", "identifications": [ { "type": "TAX_ID", "value": "12-3456789", "issuingCountry": "US" } ] }, "currency": "USD", "externalId": "erp-bank-001", "tags": ["payout"] } ``` ```typescript JavaScript SDK theme={null} interface BankAccount { id: string; orgId: string; subOrgId?: string; orgEntityId: string; createdById: string; createdAt: string; updatedAt: string; isDeleted: boolean; status: BankAccountStatus; accountName: string | null; bankAccountInfo?: BankAccountInfo | null; paymentRails: string[]; beneficiary?: BankAccountBeneficiary | null; currency: string | null; externalId: string | null; tags?: string[]; } ``` # List Bank Accounts Source: https://docs.primevault.com/bank-accounts/list-bank-accounts GET https://api.primevault.com/api/external/bank_accounts/ Retrieve a paginated list of your saved bank accounts. **GET** `/api/external/bank_accounts/?limit={limit}&cursor={cursor}&{filters}` Lists the bank accounts available to the API user's organization. Use cursor pagination to walk through large sets, and apply filters to narrow results. > [!WARNING] > > Bank-account data is sensitive. Avoid logging full account numbers, routing numbers, IBANs, and beneficiary identity documents, and preserve the organization or sub-org scope returned by the API. ## Query parameters **limit** `number` Requested page size. SDK default: 20. **cursor** `string | null` Cursor for the next page. First page: null or empty. **Filters** `Record\` Bank-account filters accepted by the endpoint. ## Response Returns a page of [BankAccount](/api-basics/data-models#bankaccount) objects. See [Cursor response](/api-basics/filtering-and-pagination#cursor-response). **results** `BankAccount[]` Bank accounts on this page. Each item is a [BankAccount](/api-basics/data-models#bankaccount). **Properties** **id** `string` Bank account ID. **orgId** `string` Organization that owns the bank account. **subOrgId** `string` [Sub-org](/sub-orgs/overview) that owns the bank account, if any. **orgEntityId** `string` Organization entity that owns the account. **createdById** `string` User or API user that created the bank account. **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the bank account is deleted. **status** `BankAccountStatus` `PENDING`, `APPROVED`, or `DECLINED`. A new bank account stays `PENDING` until it's [approved](/approvals/overview). See [BankAccountStatus](/api-basics/data-models#bankaccountstatus). **accountName** `string | null` Account-holder name. **bankAccountInfo** `BankAccountInfo | null` Bank and account identifiers. See [BankAccountInfo](/api-basics/data-models#bankaccountinfo). **Properties** **country** `string | null` Country code of the bank. **accountNumber** `string | null` Bank account number. **routingNumber** `string | null` Domestic routing identifier where applicable. **bankName** `string | null` Bank name. **bankAddress** `string | null` Bank address. **pixKey** `string | null` PIX key. Used with the `PIX` rail. **pixKeyType** `string | null` Type of the PIX key. **iban** `string | null` International Bank Account Number. **bic** `string | null` Bank Identifier Code (SWIFT code). **accountType** `string | null` `CHECKING` or `SAVINGS`. **paymentRails** `string[]` Payment rails the account supports: `ACH`, `WIRE`, `SEPA`, `SWIFT`, `FPS`, `NIP`, `PIX`, or `UAEFTS`. See [PaymentRail](/api-basics/data-models#paymentrail). **beneficiary** `BankAccountBeneficiary | null` Account holder. `entityType` decides whether `business` or `individual` is present. See [BankAccountBeneficiary](/api-basics/data-models#bankaccountbeneficiary). **Properties** **entityType** `string` `BUSINESS` or `INDIVIDUAL`. **name** `string | null` Beneficiary name. **address** `BankAccountBeneficiaryAddress` Beneficiary address. **Properties** **streetLine1** `string` First street address line. **streetLine2** `string | null` Second street address line. **city** `string` City. **state** `string | null` State or province. **zip** `string` Postal code. **country** `string` Country code. **email** `string | null` Beneficiary email address. **phoneNumber** `string | null` Beneficiary phone number. **business** `object` Present when `entityType` is `BUSINESS`. **Properties** **legalName** `string` Registered legal name of the business. **registrationCountry** `string | null` Country where the business is registered. **individual** `object` Present when `entityType` is `INDIVIDUAL`. **Properties** **firstName** `string` First name. **middleName** `string | null` Middle name. **lastName** `string` Last name. **dateOfBirth** `string | null` Date of birth. **nationality** `string | null` Nationality. **gender** `string | null` Gender. **occupation** `string | null` Occupation. **identifications** `object[]` Identity documents of the beneficiary. **Properties** **type** `string` `BUSINESS`: `COMPANY_REGISTRATION`, `LEI`, or `TAX_ID`. `INDIVIDUAL`: `PASSPORT`, `NATIONAL_ID`, or `TAX_ID`. **value** `string` Document number or identifier. **issuingCountry** `string | null` Country that issued the document. **currency** `string | null` Account currency. **externalId** `string | null` Client-controlled bank-account reference. **tags** `string[]` Tags on the bank account. **nextCursor** `string | null` Cursor for the next page. `null` on the last page. **hasNext** `boolean` Whether another page exists. ```json Request example theme={null} { "query": { "limit": 20, "cursor": null } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request GET \ --url "https://api.primevault.com/api/external/bank_accounts/?limit=20" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Accept: application/json" ``` ```typescript JavaScript SDK theme={null} getBankAccounts( params?: Record, limit?: number, cursor?: string | null, ): Promise ``` ```json Response example theme={null} { "results": [ { "id": "bank_account_123", "orgId": "org_456", "subOrgId": "sub_org_123", "orgEntityId": "org_entity_123", "createdById": "api_user_123", "createdAt": "2026-08-04T10:30:00Z", "updatedAt": "2026-08-04T10:30:00Z", "isDeleted": false, "status": "PENDING", "accountName": "Acme Corp", "bankAccountInfo": { "country": "US", "accountNumber": "111222333", "routingNumber": "021000021", "bankName": "Chase", "bankAddress": "270 Park Avenue, New York, NY 10017", "accountType": "CHECKING" }, "paymentRails": ["ACH", "WIRE"], "beneficiary": { "entityType": "BUSINESS", "name": "Acme Corp", "business": { "legalName": "Acme Corp", "registrationCountry": "US" }, "address": { "streetLine1": "100 Main Street", "streetLine2": "Suite 400", "city": "San Francisco", "state": "CA", "zip": "94105", "country": "US" }, "email": "treasury@acme.example", "identifications": [ { "type": "TAX_ID", "value": "12-3456789", "issuingCountry": "US" } ] }, "currency": "USD", "externalId": "erp-bank-001", "tags": ["payout"] } ], "nextCursor": "eyJpZCI6ImJhbmtfYWNjb3VudF8xMjMifQ==", "hasNext": true } ``` ```typescript JavaScript SDK theme={null} interface BankAccountListResponse { results: BankAccount[]; nextCursor?: string | null; hasNext?: boolean; } ``` # Retrieve a Bank Account Source: https://docs.primevault.com/bank-accounts/retrieve-bank-account GET https://api.primevault.com/api/external/bank_accounts/{bankAccountId}/ Retrieve a single bank account by ID, including its status, account details, and beneficiary. **GET** `/api/external/bank_accounts/{bankAccountId}/` Returns one bank account by its identifier. Use this to read the current status and exact account fields for a previously created bank account. > [!WARNING] > > Bank-account data is sensitive. Avoid logging full account numbers, routing numbers, IBANs, and beneficiary identity documents, and preserve the organization or sub-org scope returned by the API. ## Path parameters **bankAccountId** `string` *required* The bank account ID. ## Response Returns a [BankAccount](/api-basics/data-models#bankaccount). **id** `string` Bank account ID. **orgId** `string` Organization that owns the bank account. **subOrgId** `string` [Sub-org](/sub-orgs/overview) that owns the bank account, if any. **orgEntityId** `string` Organization entity that owns the account. **createdById** `string` User or API user that created the bank account. **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the bank account is deleted. **status** `BankAccountStatus` `PENDING`, `APPROVED`, or `DECLINED`. A new bank account stays `PENDING` until it's [approved](/approvals/overview). See [BankAccountStatus](/api-basics/data-models#bankaccountstatus). **accountName** `string | null` Account-holder name. **bankAccountInfo** `BankAccountInfo | null` Bank and account identifiers. See [BankAccountInfo](/api-basics/data-models#bankaccountinfo). **Properties** **country** `string | null` Country code of the bank. **accountNumber** `string | null` Bank account number. **routingNumber** `string | null` Domestic routing identifier where applicable. **bankName** `string | null` Bank name. **bankAddress** `string | null` Bank address. **pixKey** `string | null` PIX key. Used with the `PIX` rail. **pixKeyType** `string | null` Type of the PIX key. **iban** `string | null` International Bank Account Number. **bic** `string | null` Bank Identifier Code (SWIFT code). **accountType** `string | null` `CHECKING` or `SAVINGS`. **paymentRails** `string[]` Payment rails the account supports: `ACH`, `WIRE`, `SEPA`, `SWIFT`, `FPS`, `NIP`, `PIX`, or `UAEFTS`. See [PaymentRail](/api-basics/data-models#paymentrail). **beneficiary** `BankAccountBeneficiary | null` Account holder. `entityType` decides whether `business` or `individual` is present. See [BankAccountBeneficiary](/api-basics/data-models#bankaccountbeneficiary). **Properties** **entityType** `string` `BUSINESS` or `INDIVIDUAL`. **name** `string | null` Beneficiary name. **address** `BankAccountBeneficiaryAddress` Beneficiary address. **Properties** **streetLine1** `string` First street address line. **streetLine2** `string | null` Second street address line. **city** `string` City. **state** `string | null` State or province. **zip** `string` Postal code. **country** `string` Country code. **email** `string | null` Beneficiary email address. **phoneNumber** `string | null` Beneficiary phone number. **business** `object` Present when `entityType` is `BUSINESS`. **Properties** **legalName** `string` Registered legal name of the business. **registrationCountry** `string | null` Country where the business is registered. **individual** `object` Present when `entityType` is `INDIVIDUAL`. **Properties** **firstName** `string` First name. **middleName** `string | null` Middle name. **lastName** `string` Last name. **dateOfBirth** `string | null` Date of birth. **nationality** `string | null` Nationality. **gender** `string | null` Gender. **occupation** `string | null` Occupation. **identifications** `object[]` Identity documents of the beneficiary. **Properties** **type** `string` `BUSINESS`: `COMPANY_REGISTRATION`, `LEI`, or `TAX_ID`. `INDIVIDUAL`: `PASSPORT`, `NATIONAL_ID`, or `TAX_ID`. **value** `string` Document number or identifier. **issuingCountry** `string | null` Country that issued the document. **currency** `string | null` Account currency. **externalId** `string | null` Client-controlled bank-account reference. **tags** `string[]` Tags on the bank account. ```json Request example theme={null} { "path": { "bankAccountId": "bank_account_123" } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request GET \ --url "https://api.primevault.com/api/external/bank_accounts/bank_account_123/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Accept: application/json" ``` ```typescript JavaScript SDK theme={null} getBankAccountById(bankAccountId: string): Promise ``` ```json Response example theme={null} { "id": "bank_account_123", "orgId": "org_456", "subOrgId": "sub_org_123", "orgEntityId": "org_entity_123", "createdById": "api_user_123", "createdAt": "2026-08-04T10:30:00Z", "updatedAt": "2026-08-04T10:30:00Z", "isDeleted": false, "status": "PENDING", "accountName": "Acme Corp", "bankAccountInfo": { "country": "US", "accountNumber": "111222333", "routingNumber": "021000021", "bankName": "Chase", "bankAddress": "270 Park Avenue, New York, NY 10017", "accountType": "CHECKING" }, "paymentRails": ["ACH", "WIRE"], "beneficiary": { "entityType": "BUSINESS", "name": "Acme Corp", "business": { "legalName": "Acme Corp", "registrationCountry": "US" }, "address": { "streetLine1": "100 Main Street", "streetLine2": "Suite 400", "city": "San Francisco", "state": "CA", "zip": "94105", "country": "US" }, "email": "treasury@acme.example", "identifications": [ { "type": "TAX_ID", "value": "12-3456789", "issuingCountry": "US" } ] }, "currency": "USD", "externalId": "erp-bank-001", "tags": ["payout"] } ``` ```typescript JavaScript SDK theme={null} interface BankAccount { id: string; orgId: string; subOrgId?: string; orgEntityId: string; createdById: string; createdAt: string; updatedAt: string; isDeleted: boolean; status: BankAccountStatus; accountName: string | null; bankAccountInfo?: BankAccountInfo | null; paymentRails: string[]; beneficiary?: BankAccountBeneficiary | null; currency: string | null; externalId: string | null; tags?: string[]; } ``` # Create a Contact Source: https://docs.primevault.com/blockchain-contacts/create-contact POST https://api.primevault.com/api/external/contacts/ Save a blockchain address as a contact. The contact needs your API user's approval before it takes effect. **POST** `/api/external/contacts/` ## Request body **name** `string` *required* Contact display name. **subOrgId** `string` Sub-org that owns the contact. **address** `string` *required* Destination address on the selected chain. **blockChain** `string` *required* REST field. The SDK accepts this as `chain`. **tags** `string[]` Searchable contact tags. **externalId** `string` Client-controlled reference. **assetList** `string[]` Assets permitted for the contact. The SDK serializes an omitted value as an empty array. **contactGroupIds** `string[]` Organization-scoped contact groups to attach. ## Response Returns the new [Contact](/api-basics/data-models#contact) with `status: "PENDING"`. **id** `string` Contact ID. **orgId** `string` Organization that owns the contact. **subOrgId** `string` [Sub-org](/sub-orgs/overview) that owns the contact, if any. **name** `string` Contact display name. **blockChain** `string` Chain of the address. **address** `string` Blockchain address. **status** `ContactStatus` `PENDING`, `APPROVED`, or `DECLINED`. A new contact stays `PENDING` until it's [approved](/approvals/overview). See [ContactStatus](/api-basics/data-models#contactstatus). **isSmartContractAddress** `boolean` Whether the address is a smart contract. **tags** `string[]` Searchable contact tags. **createdById** `string` User or API user that created the contact. **isSanctioned** `boolean` Whether the address is flagged by sanctions screening. **externalId** `string` Your own reference, if you set one. **assetList** `string[]` Assets permitted for the contact. **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the contact is deleted. ## Approve the contact A new contact comes back with `status: "PENDING"` and needs your API user's approval. Call [Get Approval Message](/approvals/get-approval-message) with `entityId` set to the contact `id`, sign the `message`, and send the signature to [Submit Approval Action](/approvals/submit-approval-action). See [Approvals](/approvals/overview) for the full flow. With the SDK, approve separately or in one call: ```typescript theme={null} const contact = await apiClient.createContact(request); const approvedContact = await apiClient.createContactApproval(contact); // Convenience helper: create, then approve. const approved = await apiClient.createContactWithApproval(request); ``` ```json REST request body theme={null} { "name": "Operations Wallet", "subOrgId": "sub_org_123", "address": "0xAbC123...", "blockChain": "ETHEREUM", "tags": ["vendor", "priority"], "externalId": "vendor-1001", "assetList": ["USDC", "ETH"], "contactGroupIds": ["contact_group_123"] } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/contacts/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "name": "Operations Wallet", "subOrgId": "sub_org_123", "address": "0xAbC123...", "blockChain": "ETHEREUM", "tags": ["vendor", "priority"], "externalId": "vendor-1001", "assetList": ["USDC", "ETH"], "contactGroupIds": ["contact_group_123"] }' ``` ```typescript JavaScript SDK theme={null} interface CreateContactRequest { name: string; subOrgId?: string; address: string; chain: string; tags?: string[]; externalId?: string; assetList?: string[]; contactGroupIds?: string[]; } createContact(request: CreateContactRequest): Promise ``` ```json Response example theme={null} { "id": "contact_123", "orgId": "org_456", "subOrgId": "sub_org_123", "name": "Operations Wallet", "blockChain": "ETHEREUM", "address": "0xAbC123...", "status": "PENDING", "tags": ["vendor", "priority"], "createdById": "api_user_123", "externalId": "vendor-1001", "assetList": ["USDC", "ETH"], "createdAt": "2026-08-04T10:30:00Z", "updatedAt": "2026-08-04T10:30:00Z", "isDeleted": false } ``` ```typescript JavaScript SDK theme={null} interface Contact { id: string; orgId: string; subOrgId?: string; name: string; blockChain: string; address: string; status: ContactStatus; isSmartContractAddress: boolean; tags?: string[]; createdById: string; isSanctioned: boolean; externalId?: string; createdAt: string; updatedAt: string; isDeleted: boolean; assetList?: string[]; } ``` # List Contacts Source: https://docs.primevault.com/blockchain-contacts/list-contacts GET https://api.primevault.com/api/external/contacts/ Retrieve a paginated list of contacts, optionally filtered by name, status, or external ID. **GET** `/api/external/contacts/?limit={limit}&cursor={cursor}&{filters}` ## Query parameters **limit** `number` Requested page size. SDK default: 20. **cursor** `string | null` Cursor for the next page. **Filters** `Record\` Contact filters, for example `name`, `status`, or `externalId` when supported. ## Response Returns a page of [Contact](/api-basics/data-models#contact) objects. See [Cursor response](/api-basics/filtering-and-pagination#cursor-response). **results** `Contact[]` Contacts on this page. Each item is a [Contact](/api-basics/data-models#contact). **Properties** **id** `string` Contact ID. **orgId** `string` Organization that owns the contact. **subOrgId** `string` [Sub-org](/sub-orgs/overview) that owns the contact, if any. **name** `string` Contact display name. **blockChain** `string` Chain of the address. **address** `string` Blockchain address. **status** `ContactStatus` `PENDING`, `APPROVED`, or `DECLINED`. A new contact stays `PENDING` until it's [approved](/approvals/overview). See [ContactStatus](/api-basics/data-models#contactstatus). **isSmartContractAddress** `boolean` Whether the address is a smart contract. **tags** `string[]` Searchable contact tags. **createdById** `string` User or API user that created the contact. **isSanctioned** `boolean` Whether the address is flagged by sanctions screening. **externalId** `string` Your own reference, if you set one. **assetList** `string[]` Assets permitted for the contact. **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the contact is deleted. **nextCursor** `string | null` Cursor for the next page. `null` on the last page. **hasNext** `boolean` Whether another page exists. ```json Request example theme={null} { "query": { "limit": 20, "cursor": null, "status": "PENDING" } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request GET \ --url "https://api.primevault.com/api/external/contacts/?limit=20&status=PENDING" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Accept: application/json" ``` ```typescript JavaScript SDK theme={null} getContacts( params?: Record, limit?: number, cursor?: string | null, ): Promise const firstPage = await apiClient.getContacts( { status: "PENDING" }, 20, null, ); ``` ```json Response example theme={null} { "results": [ { "id": "contact_123", "orgId": "org_456", "subOrgId": "sub_org_123", "name": "Operations Wallet", "blockChain": "ETHEREUM", "address": "0xAbC1234567890abcdef1234567890abcdef1234", "status": "PENDING", "tags": [ "vendor", "priority" ], "createdById": "api_user_123", "externalId": "vendor-1001", "assetList": [ "USDC", "ETH" ], "createdAt": "2026-08-04T10:30:00Z", "updatedAt": "2026-08-04T10:30:00Z", "isDeleted": false } ], "nextCursor": "eyJpZCI6ImNvbnRhY3RfMTIzIn0=", "hasNext": true } ``` ```typescript JavaScript SDK theme={null} interface ContactListResponse { results: Contact[]; nextCursor?: string | null; hasNext?: boolean; } ``` # Blockchain Contacts Source: https://docs.primevault.com/blockchain-contacts/overview Save external blockchain addresses as contacts, get them approved, and use them as destinations for transfers and payouts from your vaults. A blockchain contact is a saved external wallet: a named address on a specific chain, such as a customer's or vendor's wallet. After your API user approves it, reference the contact by its `id` with `type: "CONTACT"` as the destination of a [transfer](/transactions/on-chain-operations/create-transfer) or [payout](/payment-flows/payout/payout-wallet), or as the source of an [LP vault deposit](/payment-flows/trade/deposit-into-lp-vault). Each [Contact](/api-basics/data-models#contact) records: * **Address:** `name`, `blockChain`, and `address`. * **Permitted assets:** `assetList`, the assets the contact can be used with. * **Your references:** `tags` and `externalId`. Filter lists by `name`, `status`, or `externalId`. See [Filtering and Pagination](/api-basics/filtering-and-pagination). * **Screening:** `isSanctioned`, whether the address is flagged by sanctions screening. To keep a contact inside a [sub-org](/sub-orgs/overview), pass `subOrgId` when you create it. > [!NOTE] > > **Lifecycle:** Creating or updating a contact needs your API user's approval. The contact stays `PENDING` until it's approved, then moves to `APPROVED` or `DECLINED`. See [Approvals](/approvals/overview). After creation, you can change only `assetList` and `contactGroupIds`; a contact's name and address can't be changed. > [card] [List contacts](/blockchain-contacts/list-contacts) > > **GET** `/api/external/contacts/` > [card] [Retrieve a contact](/blockchain-contacts/retrieve-contact) > > **GET** `/api/external/contacts/{contactId}/` > [card] [Create a contact](/blockchain-contacts/create-contact) > > **POST** `/api/external/contacts/` > [card] [Update a contact](/blockchain-contacts/update-contact) > > **PUT** `/api/external/contacts/{contactId}/` # Retrieve a Contact Source: https://docs.primevault.com/blockchain-contacts/retrieve-contact GET https://api.primevault.com/api/external/contacts/{contactId}/ Retrieve a single contact by ID, including its blockchain, address, status, tags, and assets. **GET** `/api/external/contacts/{contactId}/` ## Path parameters **contactId** `string` *required* The unique contact identifier. ## Response Returns a [Contact](/api-basics/data-models#contact). **id** `string` Contact ID. **orgId** `string` Organization that owns the contact. **subOrgId** `string` [Sub-org](/sub-orgs/overview) that owns the contact, if any. **name** `string` Contact display name. **blockChain** `string` Chain of the address. **address** `string` Blockchain address. **status** `ContactStatus` `PENDING`, `APPROVED`, or `DECLINED`. A new contact stays `PENDING` until it's [approved](/approvals/overview). See [ContactStatus](/api-basics/data-models#contactstatus). **isSmartContractAddress** `boolean` Whether the address is a smart contract. **tags** `string[]` Searchable contact tags. **createdById** `string` User or API user that created the contact. **isSanctioned** `boolean` Whether the address is flagged by sanctions screening. **externalId** `string` Your own reference, if you set one. **assetList** `string[]` Assets permitted for the contact. **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the contact is deleted. ```json Request example theme={null} { "path": { "contactId": "contact_123" } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request GET \ --url "https://api.primevault.com/api/external/contacts/contact_123/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Accept: application/json" ``` ```typescript JavaScript SDK theme={null} getContactById(contactId: string): Promise const contact = await apiClient.getContactById("contact_123"); ``` ```json Response example theme={null} { "id": "contact_123", "orgId": "org_456", "subOrgId": "sub_org_123", "name": "Operations Wallet", "blockChain": "ETHEREUM", "address": "0xAbC1234567890abcdef1234567890abcdef1234", "status": "PENDING", "tags": [ "vendor", "priority" ], "createdById": "api_user_123", "externalId": "vendor-1001", "assetList": [ "USDC", "ETH" ], "createdAt": "2026-08-04T10:30:00Z", "updatedAt": "2026-08-04T10:30:00Z", "isDeleted": false } ``` ```typescript JavaScript SDK theme={null} interface Contact { id: string; orgId: string; subOrgId?: string; name: string; blockChain: string; address: string; status: ContactStatus; isSmartContractAddress: boolean; tags?: string[]; createdById: string; isSanctioned: boolean; externalId?: string; createdAt: string; updatedAt: string; isDeleted: boolean; assetList?: string[]; } ``` # Update a Contact Source: https://docs.primevault.com/blockchain-contacts/update-contact PUT https://api.primevault.com/api/external/contacts/{contactId}/ Update a contact's asset list and contact groups. You can't change its name or address. The update needs your API user's approval. **PUT** `/api/external/contacts/{contactId}/` > [!NOTE] > > The current SDK update contract changes only `assetList` and `contactGroupIds`. It does not rename the contact or replace its address. ## Path parameters **contactId** `string` *required* The unique contact identifier. ## Request body **assetList** `string[]` Assets permitted for the contact. **contactGroupIds** `string[]` Organization-scoped contact groups to attach. ## Response Returns the updated contact's identifying fields, a subset of [Contact](/api-basics/data-models#contact). **id** `string` Contact ID. **name** `string` Contact display name. **address** `string` Blockchain address. **blockChain** `string` Chain of the address. **tags** `string[]` Searchable contact tags. **externalId** `string` Your own reference, if you set one. **assetList** `string[]` Assets permitted for the contact. ## Approve the update Updating a contact opens a change request that your API user must approve. Call [Get Approval Message](/approvals/get-approval-message) with `entityId` set to the contact `id`, sign the `message`, and send the signature to [Submit Approval Action](/approvals/submit-approval-action). See [Approvals](/approvals/overview) for the full flow. With the SDK, `updateContactWithApproval()` updates and approves in one call, then returns the contact: ```typescript theme={null} const contact = await apiClient.updateContactWithApproval({ id: "contact_123", assetList: ["USDC"], }); ``` ```json REST request body theme={null} { "assetList": ["USDC"], "contactGroupIds": ["contact_group_123"] } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request PUT \ --url "https://api.primevault.com/api/external/contacts/contact_123/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "assetList": ["USDC"], "contactGroupIds": ["contact_group_123"] }' ``` ```typescript JavaScript SDK theme={null} interface UpdateContactRequest { id: string; assetList?: string[]; contactGroupIds?: string[]; } updateContact(request: UpdateContactRequest): Promise updateContactWithApproval(request: UpdateContactRequest): Promise ``` ```json Response example theme={null} { "id": "contact_123", "name": "Operations Wallet", "address": "0xAbC1234567890abcdef1234567890abcdef1234", "blockChain": "ETHEREUM", "tags": [ "vendor", "priority" ], "externalId": "vendor-1001", "assetList": [ "USDC" ] } ``` ```typescript JavaScript SDK theme={null} // updateContact() returns a subset of Contact fields. type UpdateContactResponse = Pick< Contact, "id" | "name" | "address" | "blockChain" | "tags" | "externalId" | "assetList" >; ``` # Coverage Source: https://docs.primevault.com/coverage Countries, currencies and payment methods supported for payins and payouts on PrimeVault. PrimeVault lets you receive and send money in local currencies through a single API. The USD leg settles same day (T+0) over stablecoin rails, and the last mile is delivered through licensed local partners. | Currencies | Countries | Regions | USD settlement | | - | - | - | - | | 30+ | 35+ | 5 | T+0 | ## How to read this page * **Payin:** funds enter PrimeVault. Local currency is collected and converted to USDC/Stablecoin. * **Payout:** funds leave PrimeVault. USDC/Stablecoin is converted to local currency and delivered to the beneficiary. * **Bank transfer:** delivery to a bank account over the local payment rail. Settlement time varies by corridor. * **Mobile wallet:** delivery to a mobile money account, in real time or near real time, subject to provider availability. * **—** means the direction is not currently supported for that country. ## Americas North America and Latin America. | Country | Currency | Payin | Payout | | - | - | - | - | | United States | USD | Bank transfer | Bank transfer | | Canada | CAD | Bank transfer | Bank transfer | | Mexico | MXN | Bank transfer | Bank transfer | | Brazil | BRL | Bank transfer | Bank transfer | | Colombia | COP | Bank transfer | Bank transfer | | Argentina | ARS | Bank transfer | Bank transfer | ## Europe Eurozone, United Kingdom and other European currencies. | Country | Currency | Payin | Payout | | - | - | - | - | | Eurozone | EUR | Bank transfer | Bank transfer | | United Kingdom | GBP | Bank transfer | Bank transfer | | Switzerland | CHF | Bank transfer | Bank transfer | | Czech Republic | CZK | Bank transfer | Bank transfer | | Denmark | DKK | Bank transfer | Bank transfer | | Norway | NOK | Bank transfer | Bank transfer | | Poland | PLN | Bank transfer | Bank transfer | | Romania | RON | Bank transfer | Bank transfer | | Sweden | SEK | Bank transfer | Bank transfer | | Turkey | TRY | Bank transfer | Bank transfer | ## Middle East | Country | Currency | Payin | Payout | | - | - | - | - | | UAE | AED | Bank transfer | Bank transfer | ## Asia China is offshore CNH only. | Country | Currency | Payin | Payout | | - | - | - | - | | Hong Kong | HKD | Bank transfer | Bank transfer | | Singapore | SGD | Bank transfer | Bank transfer | | China (offshore) | CNH | Bank transfer | Bank transfer | | India | INR | Bank transfer | Bank transfer | | Vietnam | VND | Bank transfer | Bank transfer | ## Africa Mobile wallet and bank transfer coverage across 16 markets. Mobile wallet networks vary by country and include M-Pesa, MTN MoMo, Airtel Money, Orange Money, Wave, Tigo and Moov. | Country | Currency | Payin | Payout | | - | - | - | - | | Ghana | GHS | Mobile wallet | Mobile wallet, Bank transfer | | Kenya | KES | Mobile wallet | Mobile wallet, Bank transfer | | Uganda | UGX | Mobile wallet | Mobile wallet, Bank transfer | | Tanzania | TZS | Mobile wallet | Mobile wallet | | Ivory Coast | XOF | Mobile wallet | Mobile wallet, Bank transfer | | Senegal | XOF | Mobile wallet | Mobile wallet, Bank transfer | | Mali | XOF | Mobile wallet | Mobile wallet | | Burkina Faso | XOF | Mobile wallet | Mobile wallet | | Benin | XOF | Mobile wallet | Mobile wallet | | Togo | XOF | Mobile wallet | Mobile wallet, Bank transfer | | Cameroon | XAF | Mobile wallet | Mobile wallet, Bank transfer | | Congo | XAF | — | Mobile wallet | | Guinea | GNF | Mobile wallet | Mobile wallet | | Sierra Leone | SLL | — | Mobile wallet | | South Africa | ZAR | Bank transfer | Bank transfer | | Nigeria | NGN | Bank transfer | Bank transfer | > [!NOTE] > > Beneficiaries can be the sender's own entity (first party) or a different entity (third party) in all corridors, subject to compliance review. All transactions are subject to KYC/KYB, sanctions screening and corridor-specific limits. Delivery times reflect typical performance and are not contractual SLAs unless agreed in writing. ## Don't see your corridor? Coverage is expanding. Contact your PrimeVault representative or email [contact@primevault.com](mailto:contact@primevault.com) to ask about a specific country, currency or payment method. # Welcome to PrimeVault Source: https://docs.primevault.com/index A stablecoin-native global money movement platform that helps fintechs, PSPs and global corporations move money across their corridors better, faster and cheaper. PrimeVault is the new architecture for global money. It is a unified, intelligent layer for cross-border payments and treasury operations across local currencies, stablecoins, and digital assets. Businesses use PrimeVault to collect, hold, convert and move money across borders, in stablecoins and other digital assets alongside local currencies, with governance and compliance built into every transaction. ## What you can do on PrimeVault > [card] Collect > > Accept pay-ins in local currency or stablecoins across 50+ markets. > [card] Hold > > Keep balances in multiple currencies and stablecoins > [card] Convert > > Move between currencies, and between fiat and stablecoins. > [card] Move > > Pay out cross-border, one at a time or in batches. > [card] Smart Treasury > > Monitor exposure and automate fund movements. > [card] Earn > > Put idle balances to work in T-bills and onchain strategies. # Multi-currency accounts Source: https://docs.primevault.com/payment-flows/accounts/multi-currency-accounts Receive, hold, and send fiat from a dedicated account per currency, like a virtual account. A multi-currency account is a set of [vaults](/vaults/overview), one per currency. Each vault holds a single fiat currency, set by its `asset` field: an NGN vault has `asset: "NGN"`, and a EUR vault has `asset: "EUR"`. Each vault works like a virtual account. You get bank details to receive into it, it keeps its own balance, and you pay out from it. ## How it works | Step | What happens | API | | - | - | - | | Receive | Get the vault's bank details for its currency and payment rail, and share them with the payer. Incoming transfers land in the vault. | [Deposit Instructions](/vaults/vault-deposit-instructions) | | Hold | The vault keeps a balance in its currency until you move it. | [Balances](/vaults/vault-balances) | | Send | Pay out to a bank account in the same currency, or convert on the way out. | [Pay out to a bank account](/payment-flows/payout/payout-bank-account) | | Convert | Move funds between currencies, for example NGN to EUR. | [FX](/transactions/fx) | ## Example A business that collects in Nigeria and Europe holds an NGN vault and a EUR vault. Customers pay into each vault's bank details, and each balance builds up in its own currency. The business pays EUR suppliers from the EUR vault, or converts NGN to EUR first. > [!NOTE] > > Use deposit details exactly as the API returns them. A vault can return several options, for example one per payment rail. You can't set a vault's `asset` when you [create a vault](/vaults/create-vault). > [card] [Collection fiat](/payment-flows/collection/collection-fiat) > > Collect fiat into a vault by bank transfer. > [card] [Self-custody accounts](/payment-flows/accounts/self-custody-accounts) > > Hold crypto in blockchain wallets you control. # Self-custody accounts Source: https://docs.primevault.com/payment-flows/accounts/self-custody-accounts Hold crypto in blockchain wallets you control, with one vault covering several chains. A self-custody account is a [vault](/vaults/overview) that holds crypto in blockchain wallets you control. The vault has a wallet for each chain you request, listed in `wallets`. Its `asset` is `null` because it can hold any supported asset on those chains. ## How it works | Step | What happens | API | | - | - | - | | Create | Create a vault with the chains you need. Wallet addresses can arrive after the response, so wait until `walletsGenerated` is `true`. | [Create a Vault](/vaults/create-vault) | | Receive | Share the wallet address for the asset's chain. | [Collection crypto](/payment-flows/collection/collection-crypto) | | Hold | The vault keeps a balance per asset and chain. | [Balances](/vaults/vault-balances) | | Send | Transfer to a contact or another vault, or convert on the way out. Every outgoing transaction waits for your [approval](/approvals/overview). | [Pay out to a wallet](/payment-flows/payout/payout-wallet) | ## Example A treasury team keeps USDC on Ethereum and Polygon in one vault. Customers deposit to the vault's address on either chain. The team sends USDC to a saved contact with a transfer, or converts it to USD and pays out to a bank account. > [card] [Collection crypto](/payment-flows/collection/collection-crypto) > > Collect crypto from an external wallet. > [card] [Multi-currency accounts](/payment-flows/accounts/multi-currency-accounts) > > Receive, hold, and send fiat per currency. # Collection crypto Source: https://docs.primevault.com/payment-flows/collection/collection-crypto Collect a stablecoin or other crypto asset into a vault. Collect crypto from an external wallet into a vault, for example USDC on Ethereum. ## How it works 1. Get the vault's collection address for the asset and chain. 2. Send the asset to that address. 3. The collected funds show up in the vault's balance. See [Deposit Instructions](/vaults/vault-deposit-instructions) for the API. # Collection fiat Source: https://docs.primevault.com/payment-flows/collection/collection-fiat Collect fiat into a vault by bank transfer. Collect fiat from a bank account into a vault, for example NGN over NIP. ## How it works 1. Get the vault's bank collection details for the currency and payment rail. 2. Send the bank transfer using those details. 3. The collected funds show up in the vault's balance. See [Deposit Instructions](/vaults/vault-deposit-instructions) for the API. # Use Cases Source: https://docs.primevault.com/payment-flows/overview Find the flow that matches where your money starts and where it needs to go. Every flow on PrimeVault is one of three operations: * **Collection:** fund a vault. * **Payout:** send funds from a vault to a bank account, a wallet, or another vault. It can stay in the same currency or convert on the way out. * **Trade:** buy or sell crypto with a liquidity provider (LP), using funds held in an LP vault. ## Accounts Funds live in [vaults](/vaults/overview). There are two kinds of account: | Account | Holds | Works like | | - | - | - | | [Multi-currency account](/payment-flows/accounts/multi-currency-accounts) | One fiat currency per vault, for example NGN or EUR | A virtual account: receive, hold, and send fiat | | [Self-custody account](/payment-flows/accounts/self-custody-accounts) | Crypto on one or more chains | Blockchain wallets you control | ## Choose a flow | You have | You want | Use | | - | - | - | | Fiat outside PrimeVault | Fiat in a vault | [Collection fiat](/payment-flows/collection/collection-fiat) | | Crypto outside PrimeVault | Crypto in a vault | [Collection crypto](/payment-flows/collection/collection-crypto) | | Funds in a vault | Fiat in a bank account, in the same or a different currency | [Pay out to a bank account](/payment-flows/payout/payout-bank-account) | | Funds in a vault | Crypto in a wallet, in the same or a different asset | [Pay out to a wallet](/payment-flows/payout/payout-wallet) | | Crypto in an LP vault | Fiat | [Sell crypto for fiat](/payment-flows/trade/sell-crypto) | | Fiat in an LP vault | Crypto | [Buy crypto with fiat](/payment-flows/trade/buy-crypto) | | Funds outside an LP vault | The same funds in an LP vault | [Deposit into the LP vault](/payment-flows/trade/deposit-into-lp-vault) | | Funds in an LP vault | The same funds in a contact, vault, or bank account | [Withdraw from the LP vault](/payment-flows/trade/withdraw-from-lp-vault) | Most withdrawals, and all trades, are described as an intent. Each of those pages shows example intents and links to the API. # Pay out to a bank account Source: https://docs.primevault.com/payment-flows/payout/payout-bank-account Send fiat to a bank account, in the same currency or converted from another asset. Pay out from a vault to a bank account. The payout can stay in the currency the vault holds, or convert on the way out, for example USDC to USD. Both use the intent flow. The destination can also be another vault. ## Same currency Example: pay 5,000 USD from a USD vault to a supplier's bank account over ACH. ```json theme={null} { "intent": { "input": { "asset": "USD", "amount": "5000.00" }, "output": { "asset": "USD" }, "source": { "type": "VAULT", "id": "vault_usd_settlement" }, "destination": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_supplier", "paymentRail": "ACH" } } } ``` ## Different currency Example: pay USD into a payroll bank account over ACH, funded with 10,000 USDC from a treasury vault. ```json theme={null} { "intent": { "input": { "asset": "USDC", "amount": "10000.00" }, "output": { "asset": "USD", "vaultId": "vault_usd_settlement" }, "source": { "type": "VAULT", "id": "vault_usdc_treasury", "chain": "ETHEREUM" }, "destination": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_payroll", "paymentRail": "ACH" } } } ``` Get a quote with the intent, execute it, and track the transaction to completion. See [Ramps](/transactions/ramps) for the API. # Pay out to a wallet Source: https://docs.primevault.com/payment-flows/payout/payout-wallet Send crypto to an external wallet, in the same asset or converted from another currency. Pay out from a vault to a contact, which is a saved external wallet. The destination can also be another vault. ## Same asset Example: send 500 USDC from a vault to a contact on Ethereum. Same-asset crypto payouts use a transfer instead of an intent. See [Create Transfer](/transactions/on-chain-operations/create-transfer) for the API. ## Different currency Example: convert 10,000 USD from a vault to USDC and send it to a contact on Ethereum. ```json theme={null} { "intent": { "input": { "asset": "USD", "amount": "10000.00" }, "output": { "asset": "USDC" }, "source": { "type": "VAULT", "id": "vault_usd_settlement" }, "destination": { "type": "CONTACT", "id": "contact_456", "chain": "ETHEREUM" } } } ``` Get a quote with the intent, execute it, and track the transaction to completion. See [Ramps](/transactions/ramps) for the API. # Buy crypto with fiat Source: https://docs.primevault.com/payment-flows/trade/buy-crypto Buy a stablecoin with fiat from a liquidity provider. Buy crypto with fiat from a liquidity provider (LP), for example 5,000 USDC with USD. Put the amount you want to receive on `output`. The quote fills in what it costs. ## Example intent ```json theme={null} { "intent": { "input": { "asset": "USD" }, "output": { "asset": "USDC", "amount": "5000.00" } } } ``` Get a quote with the intent, execute it before the quote expires, and track the transaction to completion. See [Trade](/transactions/trade) for the API. # Deposit into the LP vault Source: https://docs.primevault.com/payment-flows/trade/deposit-into-lp-vault Fund a liquidity provider vault so you can trade. Fund an LP vault from one of your vaults, a contact, an external address, or a bank account. A deposit uses the same quote flow as a trade: the LP vault is the `destination`, and `input` and `output` are the same asset, quoted 1:1 with no fee. After you execute the quote, send the funds using the deposit instructions on the transaction. The deposit completes when the LP confirms them. ## From one of your vaults Example: deposit 5,000 USDC from a treasury vault. ```json theme={null} { "intent": { "input": { "asset": "USDC", "amount": "5000.00" }, "output": { "asset": "USDC" }, "source": { "type": "VAULT", "id": "vault_usdc_treasury", "chain": "ETHEREUM" }, "destination": { "type": "VAULT", "id": "vault_gsr_lp" } } } ``` ## From a contact Only `source` changes: ```json theme={null} "source": { "type": "CONTACT", "id": "contact_usdt_desk", "chain": "ETHEREUM" } ``` ## From an external address Only `source` changes: ```json theme={null} "source": { "type": "EXTERNAL_ADDRESS", "chain": "ETHEREUM" } ``` ## USD by wire from a bank account USD moves only by wire, from an approved bank account. Wire the amount to the bank details in the deposit instructions. ```json theme={null} { "intent": { "input": { "asset": "USD", "amount": "25000.00" }, "output": { "asset": "USD" }, "source": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_treasury", "paymentRail": "WIRE" }, "destination": { "type": "VAULT", "id": "vault_gsr_lp" } } } ``` Get a quote with the intent, execute it before the quote expires, and track the transaction to completion. See [Trade](/transactions/trade) for the API. # Sell crypto for fiat Source: https://docs.primevault.com/payment-flows/trade/sell-crypto Sell a stablecoin for fiat with a liquidity provider. Sell crypto for fiat with a liquidity provider (LP), for example 10,000 USDT for USD. PrimeVault quotes every LP vault you have for the pair and returns the best quote first. A quote is firm for 30 seconds. ## Example intent ```json theme={null} { "intent": { "input": { "asset": "USDT", "amount": "10000.00" }, "output": { "asset": "USD" } } } ``` No source or destination is needed. The quote supplies the LP vault. Get a quote with the intent, execute it before the quote expires, and track the transaction to completion. See [Trade](/transactions/trade) for the API. # Withdraw from the LP vault Source: https://docs.primevault.com/payment-flows/trade/withdraw-from-lp-vault Move funds out of a liquidity provider vault to a contact, one of your vaults, or a bank account. Withdraw from an LP vault to a contact, one of your vaults, or a bank account. A withdrawal uses the same quote flow as a trade: the LP vault is the `source`, and `input` and `output` are the same asset, quoted 1:1 with no fee. The withdrawal completes when the LP sends the funds. ## To a contact Example: withdraw 2,000 USDT to a contact. ```json theme={null} { "intent": { "input": { "asset": "USDT" }, "output": { "asset": "USDT", "amount": "2000.00" }, "source": { "type": "VAULT", "id": "vault_gsr_lp" }, "destination": { "type": "CONTACT", "id": "contact_usdt_desk", "chain": "ETHEREUM" } } } ``` ## To one of your vaults Only `destination` changes: ```json theme={null} "destination": { "type": "VAULT", "id": "vault_usdc_treasury", "chain": "ETHEREUM" } ``` ## USD by wire to a bank account USD moves only by wire, to an approved bank account. ```json theme={null} { "intent": { "input": { "asset": "USD" }, "output": { "asset": "USD", "amount": "10000.00" }, "source": { "type": "VAULT", "id": "vault_gsr_lp" }, "destination": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_treasury", "paymentRail": "WIRE" } } } ``` Get a quote with the intent, execute it before the quote expires, and track the transaction to completion. See [Trade](/transactions/trade) for the API. # Create a Sub-Org Source: https://docs.primevault.com/sub-orgs/create-sub-org POST https://api.primevault.com/api/external/sub_orgs/ Create a sub-org with a unique name. Requires the ADMIN or OWNER role. **POST** `/api/external/sub_orgs/` Create a managed end-user SubOrg in the authenticated organization. Creation requires organization-wide access as described above. ## Request body **name** `string` *required* Non-empty SubOrg name. The name must be unique within the organization. ## Response Returns the new [SubOrg](/sub-orgs/overview#suborg). **id** `string` Sub-org ID. Pass it as `subOrgId` on other resources. **orgId** `string` Parent organization. **name** `string` Sub-org name. Unique within the organization. **controlMode** `SubOrgControlMode | null` `MANAGED` or `INDEPENDENT`. Sub-orgs created through the API are `MANAGED`. **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Deletion flag. Listed sub-orgs are not deleted. **version** `number` Record version number. ```json Request body theme={null} { "name": "PrimeVault customer" } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/sub_orgs/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "name": "PrimeVault customer" }' ``` ```typescript JavaScript SDK theme={null} const subOrg = await apiClient.createSubOrg({ name: "PrimeVault customer", }); console.log(subOrg.id, subOrg.controlMode); ``` ```python Python SDK theme={null} from primevault_python_sdk.types import CreateSubOrgRequest sub_org = api_client.create_sub_org( CreateSubOrgRequest(name="PrimeVault customer") ) print(sub_org.id, sub_org.controlMode) ``` ```json Response example theme={null} { "id": "a648fa0d-4ca9-4f4f-a520-7ef5e9db34eb", "orgId": "a30d5e99-7076-45da-a933-0d5642619340", "name": "PrimeVault customer", "controlMode": "MANAGED", "createdAt": "2026-09-14T12:00:00Z", "updatedAt": "2026-09-14T12:00:00Z", "isDeleted": false, "version": 1 } ``` ```typescript JavaScript SDK theme={null} interface SubOrg { id: string; orgId: string; name: string; controlMode: SubOrgControlMode | null; createdAt: string; updatedAt: string; isDeleted: boolean; version: number; } ``` # List Sub-Orgs Source: https://docs.primevault.com/sub-orgs/list-sub-orgs GET https://api.primevault.com/api/external/sub_orgs/ Retrieve a paginated list of sub-orgs, optionally filtered by name. **GET** `/api/external/sub_orgs/?limit={limit}&cursor={cursor}&name={name}` Returns the sub-orgs visible to the authenticated API user. Results are scoped to the organization, exclude deleted rows, and are ordered newest first by creation time, with ID as the tie-breaker. ## Query parameters **name** `string` Case-insensitive SQL-pattern match. Use `%PrimeVault%` to match names containing PrimeVault, or `PrimeVault%` for a prefix. `%` matches any sequence and `_` matches one character. **limit** `integer` Page size. Default: 20; effective range: 1-100. **cursor** `string` Opaque cursor returned as `nextCursor`. Omit it or send an empty value for the first page. ## Response Returns a page of [SubOrg](/sub-orgs/overview#suborg) objects. See [Cursor response](/api-basics/filtering-and-pagination#cursor-response). **results** `SubOrg[]` Sub-orgs on this page. Each item is a [SubOrg](/sub-orgs/overview#suborg). **Properties** **id** `string` Sub-org ID. Pass it as `subOrgId` on other resources. **orgId** `string` Parent organization. **name** `string` Sub-org name. Unique within the organization. **controlMode** `SubOrgControlMode | null` `MANAGED` or `INDEPENDENT`. Sub-orgs created through the API are `MANAGED`. **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Deletion flag. Listed sub-orgs are not deleted. **version** `number` Record version number. **nextCursor** `string | null` Cursor for the next page. `null` on the last page. **hasNext** `boolean` Whether another page exists. ```json Request example theme={null} { "query": { "limit": 20, "name": "%PrimeVault%" } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request GET \ --url "https://api.primevault.com/api/external/sub_orgs/?limit=20&name=%25PrimeVault%25" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Accept: application/json" ``` ```typescript JavaScript SDK theme={null} getSubOrgs( params?: Record, limit?: number, cursor?: string | null, ): Promise ``` ```typescript JavaScript SDK: all pages theme={null} import type { SubOrg } from "@primevault/js-api-sdk"; const subOrgs: SubOrg[] = []; let cursor: string | null = null; do { const page = await apiClient.getSubOrgs( { name: "%PrimeVault%" }, 20, cursor, ); subOrgs.push(...page.results); cursor = page.nextCursor; } while (cursor); ``` ```python Python SDK: all pages theme={null} from typing import List, Optional from primevault_python_sdk.types import SubOrg sub_orgs: List[SubOrg] = [] cursor: Optional[str] = None while True: page = api_client.get_sub_orgs( {"name": "%PrimeVault%"}, limit=20, cursor=cursor ) sub_orgs.extend(page.results) if not page.hasNext: break cursor = page.nextCursor ``` ```json Response example theme={null} { "results": [ { "id": "a648fa0d-4ca9-4f4f-a520-7ef5e9db34eb", "orgId": "a30d5e99-7076-45da-a933-0d5642619340", "name": "PrimeVault customer", "controlMode": "MANAGED", "createdAt": "2026-09-14T12:00:00Z", "updatedAt": "2026-09-14T12:00:00Z", "isDeleted": false, "version": 1 } ], "nextCursor": null, "hasNext": false } ``` ```typescript JavaScript SDK theme={null} interface SubOrgListResponse { results: SubOrg[]; nextCursor?: string | null; hasNext?: boolean; } ``` > [!NOTE] > > When `hasNext` is true, pass `nextCursor` unchanged into the next request. Keep the same name filter and page size while paging. An empty result is `results: []`, `nextCursor: null`, and `hasNext: false`. This response does not include a total count. # Sub-Orgs Source: https://docs.primevault.com/sub-orgs/overview Sub-accounts inside your organization. Use them to onboard your customers or keep business units separate. A **sub-org** is a sub-account inside your PrimeVault organization. It follows the same model as a bank's master account with sub-accounts: your organization is the master, and each sub-org holds its own vaults, contacts, bank accounts, and transactions, separate from every other sub-org. ```mermaid theme={null} flowchart TD Org["Your organization"] --> A["Sub-org: Customer A"] Org --> B["Sub-org: Customer B"] A --> A1["Vaults, contacts, bank accounts, transactions"] B --> B1["Vaults, contacts, bank accounts, transactions"] ``` ## When to use a sub-org * **Onboard your customers.** Create one sub-org per customer so each customer's funds, counterparties, and activity stay separate. * **Separate business units or entities.** Give each legal entity, region, or product line its own sub-org. * **Scope access.** Manage who can see and act on each sub-org separately, while you keep one view across the whole organization. ## How it works 1. [Create a sub-org](/sub-orgs/create-sub-org) with a name that is unique in your organization. 2. Pass its `id` as `subOrgId` when you create [vaults](/vaults/create-vault), [contacts](/blockchain-contacts/create-contact), and [bank accounts](/bank-accounts/create-bank-account), and when you get quotes or create transactions. 3. [List sub-orgs](/sub-orgs/list-sub-orgs) to find their IDs later. > [!NOTE] > > Only API users with the `ADMIN` or `OWNER` role and organization-wide access can create sub-orgs. Your organization comes from authentication, so you can't act on another organization's sub-orgs. ## Limits * The API can create and list sub-orgs. It can't retrieve a single sub-org by ID, update one, or delete one. * Sub-orgs created through the API are `MANAGED`. > [card] [Create a Sub-Org](/sub-orgs/create-sub-org) > > **POST** `/api/external/sub_orgs/` > [card] [List Sub-Orgs](/sub-orgs/list-sub-orgs) > > **GET** `/api/external/sub_orgs/` ## Data models ### SubOrg **id** `string (UUID)` Sub-org ID. Pass it as `subOrgId` on other resources. **orgId** `string (UUID)` Parent organization. **name** `string` Sub-org name. Unique within the organization. **controlMode** `SubOrgControlMode | null` `MANAGED` or `INDEPENDENT`. Some existing sub-orgs return `null`. Read-only. Sub-orgs created through the API are `MANAGED`. **createdAt** `string (date-time)` Creation timestamp. **updatedAt** `string (date-time)` Last update timestamp. **isDeleted** `boolean` Deletion flag. Listed sub-orgs are not deleted. ### CreateSubOrgRequest One required field: `name: string`. ### SubOrgListResponse A paginated list: `results: SubOrg[]`, `nextCursor: string | null`, and `hasNext: boolean`. ## SDK methods | Operation | JavaScript | Python | Returns | | - | - | - | - | | Create | `createSubOrg` | `create_sub_org` | `SubOrg` | | List | `getSubOrgs` | `get_sub_orgs` | `SubOrgListResponse` | ## Errors | Status | When it occurs | | - | - | | 400 Bad Request | Missing or invalid name, duplicate name, any supplied `controlMode`, unsupported `MAIN` creation, or an invalid pagination cursor. | | 401 Unauthorized | Missing or invalid authentication, or an inactive API user. | | 403 Forbidden | A role other than `ADMIN` or `OWNER`, an unavailable or inactive assigned home, or creation without organization-wide access. | # FX Source: https://docs.primevault.com/transactions/fx FX converts one fiat currency to another, such as NGN to USD or GBP to EUR. Request a quote, execute it, then track the transaction to completion. FX converts one fiat currency to another, such as NGN to USD or GBP to EUR. Both sides of the conversion must be fiat currencies. If one side is a digital currency such as USDC, see [Ramps](/transactions/ramps). ## API flow **1. Get a quote** Describe the source and destination bank accounts, the currency pair, and exactly one amount. Save the `quoteId` of the quote you choose. See [Get Quote](/transactions/fx/get-quote). **2. Execute the quote** Create a transaction from the `quoteId`. See [Execute Quote](/transactions/fx/execute-quote). **3. Approve the transaction** The new transaction is `PENDING` until approved. [Get the approval message](/approvals/get-approval-message), sign it, and [submit the approval](/approvals/submit-approval-action). The SDK's `createTransactionFromIntent()` does this for you. See [Approvals](/approvals/overview). **4. Track the transaction** Use [Retrieve Transaction](/transactions/retrieve-transaction) or [webhooks](/api-basics/webhooks) to track it until it reaches `COMPLETED`, `FAILED`, or `DECLINED`. > [card] [Get Quote](/transactions/fx/get-quote) > [card] [Execute Quote](/transactions/fx/execute-quote) # Execute an FX Quote Source: https://docs.primevault.com/transactions/fx/execute-quote POST https://api.primevault.com/api/external/transactions/intent/create/ Create an FX transaction from a selected quoteId. Returns HTTP 201 with the full Transaction, including deposit instructions when funding is required. **POST** `/api/external/transactions/intent/create/` **Example:** executes the FX quote `quote_fx_invoice_1042`, spending 15,000,000 NGN to pay a 10,000 USD supplier invoice. ## Request body **quoteId** `string` *required* The `quoteId` returned by [Get Quote](/transactions/fx/get-quote). **intent** `null` Send `null` when executing a quote. Do not repeat the quote intent. **externalId** `string` Client reference. **memo** `string` Human-readable note. ## Response Returns HTTP 201 with a full [Transaction](/api-basics/data-models#transaction). Optional fields depend on the payment route and status. **id** `string` Transaction ID. Use it with [Retrieve Transaction](/transactions/retrieve-transaction) to track status. **status** `string` Lifecycle status. Track it until `COMPLETED`, `FAILED`, or `DECLINED`. **quoteResponse** `QuoteResponseItem` The executed quote, including its `input`, `output`, `source`, `destination`, and `expiresAt`. See [QuoteResponseItem](/api-basics/data-models#quoteresponseitem). **source** `TransferPartyData` Where the funds come from, as a [TransferPartyData](/api-basics/data-models#transferpartydata). Which fields are present depends on `type`. **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **destination** `TransferPartyData` Where the funds go. Same fields as `source`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **balanceChanges** `TransactionOperationBalanceChanges | null` Aggregate balance movements across the transaction, when present: the input asset debited and the output asset credited. See [TransactionOperationBalanceChanges](/api-basics/data-models#transactionoperationbalancechanges). **Properties** **changes** `TransactionOperationBalanceChange[]` One entry per party and asset. See [TransactionOperationBalanceChange](/api-basics/data-models#transactionoperationbalancechange). **Properties** **party** `TransferPartyData | null` Party whose balance changed. Same fields as `source`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **asset** `string` Asset symbol. **amount** `string` Signed decimal string: negative for a debit, positive for a credit. **chain** `string` Blockchain, for crypto movements. **paymentRail** `string` Payment rail, for fiat movements. **depositInstructions** `DepositInstructions` Present when funding is required. Use the exact bank details and reference returned. See [DepositInstructions](/api-basics/data-models#depositinstructions). > [!NOTE] > > Executing a quote creates a transaction in `PENDING` that waits for approval. [Get the approval message](/approvals/get-approval-message) for the transaction `id`, sign it, then [submit the approval](/approvals/submit-approval-action). The SDK's `createTransactionFromIntent()` does this for you and returns the updated transaction. See [Approvals](/approvals/overview). ```json Request example theme={null} { "intent": null, "quoteId": "quote_fx_invoice_1042", "externalId": "supplier-invoice-1042", "memo": "Pay USD 10000 supplier invoice from NGN account" } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/transactions/intent/create/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "intent": null, "quoteId": "quote_fx_invoice_1042", "externalId": "supplier-invoice-1042", "memo": "Pay USD 10000 supplier invoice from NGN account" }' ``` ```typescript JavaScript SDK theme={null} const payment = await apiClient.createTransactionFromIntent({ quoteId: "quote_fx_invoice_1042", externalId: "supplier-invoice-1042", memo: "Pay USD 10000 supplier invoice from NGN account", }); console.log(payment.id, payment.status); ``` ```json Response example theme={null} { "id": "transaction_fx_invoice_1042", "orgId": "org_456", "vaultId": "vault_ngn_business", "status": "PENDING", "transactionType": "OUTGOING", "category": "FX", "createdAt": "2026-08-04T10:40:00Z", "updatedAt": "2026-08-04T10:40:00Z", "isDeleted": false, "externalId": "supplier-invoice-1042", "memo": "Pay USD 10000 supplier invoice from NGN account", "source": { "type": "BANK_ACCOUNT", "id": "bank_account_ngn_business", "paymentRail": "NIP" }, "destination": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_supplier", "paymentRail": "ACH" }, "balanceChanges": { "changes": [ { "party": { "type": "BANK_ACCOUNT", "id": "bank_account_ngn_business" }, "asset": "NGN", "amount": "-15000000.00", "paymentRail": "NIP" }, { "party": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_supplier" }, "asset": "USD", "amount": "10000.00", "paymentRail": "ACH" } ] }, "fees": { "amount": "22500.00", "asset": "NGN" }, "quoteResponse": { "quoteId": "quote_fx_invoice_1042", "fees": { "amount": "22500.00", "asset": "NGN" }, "input": { "asset": "NGN", "amount": "15000000.00", "vaultId": "vault_ngn_business" }, "output": { "asset": "USD", "amount": "10000.00", "vaultId": "vault_usd_settlement" }, "source": { "type": "BANK_ACCOUNT", "id": "bank_account_ngn_business", "paymentRail": "NIP" }, "destination": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_supplier", "paymentRail": "ACH" }, "expiresAt": "2026-09-30T10:00:30+00:00" }, "depositInstructions": { "type": "BANK_ACCOUNT", "paymentRail": "NIP", "bankDetails": { "bankName": "Example Settlement Bank", "beneficiaryName": "Example settlement beneficiary", "accountNumber": "" }, "asset": "NGN", "memo": "" } } ``` ```typescript JavaScript SDK theme={null} interface Transaction { id: string; orgId: string; vaultId: string; status: TransactionStatus | string; transactionType: TransactionType | string; category: TransactionCategory | string; subCategory: TransactionSubCategory | string; createdAt: string; updatedAt: string; isDeleted: boolean; amount: string; txHash?: string; error?: string; externalId?: string; createdById?: string; fees?: Fees; memo?: string; txnSignature?: string; txnSignatureData?: Record; output?: TransactionOutput; amountInUSD?: string; nonce?: number; dAppId?: string; source?: TransferPartyData; destination?: TransferPartyData; quoteResponse?: QuoteResponseItem; depositInstructions?: DepositInstructions; operations?: TransactionOperation[]; balanceChanges?: TransactionOperationBalanceChanges | null; blockChain?: string; asset?: string; /** Deprecated: use destination.address. */ toAddress?: string; /** Deprecated: use destination.name. */ toAddressName?: string; /** Deprecated: use destination.id. */ toVaultId?: string; /** Deprecated: use source.address. */ sourceAddress?: string; } ``` # Get an FX Quote Source: https://docs.primevault.com/transactions/fx/get-quote POST https://api.primevault.com/api/external/transactions/v2/quote/ Request priced quotes to convert one fiat currency to another by describing the source, destination, assets, and exactly one amount. **POST** `/api/external/transactions/v2/quote/` **Example:** an FX quote to pay a supplier exactly 10,000 USD over ACH, funded in NGN from a Nigerian business bank account over NIP. ## Request body **intent** `TransactionIntentRequest` *required* Quote criteria for the requested FX conversion. See [TransactionIntentRequest](/api-basics/data-models#transactionintentrequest). **Properties** **intent.input** `IntentAsset` *required* What you spend. See [IntentAsset](/api-basics/data-models#intentasset). **Properties** **intent.input.asset** `string` *required* Fiat currency code, such as `NGN` or `GBP`. **intent.input.amount** `string` Decimal amount to spend. Provide either this or `intent.output.amount`, not both. **intent.input.vaultId** `string` Vault that holds the input currency. **intent.output** `IntentAsset` *required* What you receive. See [IntentAsset](/api-basics/data-models#intentasset). **Properties** **intent.output.asset** `string` *required* Fiat currency code, such as `USD` or `EUR`. **intent.output.amount** `string` Decimal amount to receive. Provide either this or `intent.input.amount`, not both. **intent.output.vaultId** `string` Vault that receives the output currency. **intent.source** `TransferPartyData` *required* Source endpoint, typically a `BANK_ACCOUNT`. Set its `paymentRail` and omit `chain`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **intent.destination** `TransferPartyData` *required* Destination endpoint, typically a `BANK_ACCOUNT`. Set its `paymentRail` and omit `chain`. See [TransferPartyData](/api-basics/data-models#transferpartydata). > [!NOTE] > > Both `input.asset` and `output.asset` must be fiat currencies. If one side is a digital asset such as USDC, use [Ramps](/transactions/ramps/get-quote) instead. Supply exactly one non-null amount. The input and output vaults must belong to the same sub-org. ## Response **quotes** `QuoteResponseItem[]` Available quotes for the intent. See [QuoteResponseItem](/api-basics/data-models#quoteresponseitem). **Properties** **quoteId** `string` Quote identifier. Pass it to [Execute Quote](/transactions/fx/execute-quote). **rate** `string | null` Quoted conversion rate. **fees** `Fees | null` Fee `amount`, `asset`, and optional `amountInFiat`. See [Fees](/api-basics/data-models#fees). **input** `IntentAsset` Quoted input currency and amount. See [IntentAsset](/api-basics/data-models#intentasset). **output** `IntentAsset` Quoted output currency and amount. See [IntentAsset](/api-basics/data-models#intentasset). **source** `TransferPartyData` Source endpoint for the conversion. See [TransferPartyData](/api-basics/data-models#transferpartydata). **destination** `TransferPartyData` Destination endpoint for the conversion. See [TransferPartyData](/api-basics/data-models#transferpartydata). **expiresAt** `string` ISO 8601 time when the quote expires. Execute the quote before this time. ```json Request example theme={null} { "intent": { "input": { "asset": "NGN", "vaultId": "vault_ngn_business" }, "output": { "asset": "USD", "amount": "10000.00", "vaultId": "vault_usd_settlement" }, "source": { "type": "BANK_ACCOUNT", "id": "bank_account_ngn_business", "paymentRail": "NIP" }, "destination": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_supplier", "paymentRail": "ACH" } } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/transactions/v2/quote/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "intent": { "input": { "asset": "NGN", "vaultId": "vault_ngn_business" }, "output": { "asset": "USD", "amount": "10000.00", "vaultId": "vault_usd_settlement" }, "source": { "type": "BANK_ACCOUNT", "id": "bank_account_ngn_business", "paymentRail": "NIP" }, "destination": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_supplier", "paymentRail": "ACH" } } }' ``` ```typescript JavaScript SDK theme={null} const quoteResponse = await apiClient.getQuote({ intent: { input: { asset: "NGN", vaultId: "vault_ngn_business" }, output: { asset: "USD", amount: "10000.00", vaultId: "vault_usd_settlement" }, source: { type: "BANK_ACCOUNT", id: "bank_account_ngn_business", paymentRail: "NIP" }, destination: { type: "BANK_ACCOUNT", id: "bank_account_usd_supplier", paymentRail: "ACH" }, }, }); console.log(quoteResponse.quotes); ``` ```json Response example theme={null} { "quotes": [ { "quoteId": "quote_fx_invoice_1042", "fees": { "amount": "22500.00", "asset": "NGN" }, "input": { "asset": "NGN", "amount": "15000000.00", "vaultId": "vault_ngn_business" }, "output": { "asset": "USD", "amount": "10000.00", "vaultId": "vault_usd_settlement" }, "source": { "type": "BANK_ACCOUNT", "id": "bank_account_ngn_business", "paymentRail": "NIP" }, "destination": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_supplier", "paymentRail": "ACH" }, "expiresAt": "2026-09-30T10:00:30+00:00" } ] } ``` ```typescript JavaScript SDK theme={null} interface QuoteResponse { quotes: QuoteResponseItem[]; } interface QuoteResponseItem { quoteId: string; rate?: string | null; fees?: Fees | null; input?: IntentAsset; output?: IntentAsset; source?: TransferPartyData; destination?: TransferPartyData; expiresAt?: string; } ``` # List Transactions Source: https://docs.primevault.com/transactions/list-transactions GET https://api.primevault.com/api/external/transactions/ Retrieve a paginated list of transactions, filtered by status, category, vault, asset, or external ID. **GET** `/api/external/transactions/?limit={limit}&cursor={cursor}&{filters}` ## Query parameters **limit** `number` Requested page size. SDK default: 20. **cursor** `string | null` First page: null/empty. Subsequent pages: the preceding `nextCursor`. **Filters** `Record\` Transaction filters such as status, category, vaultId, asset, or externalId when supported. ## Response Returns a page of [Transaction](/api-basics/data-models#transaction) objects. See [Cursor response](/api-basics/filtering-and-pagination#cursor-response). **results** `Transaction[]` Transactions on this page. Each item is a [Transaction](/api-basics/data-models#transaction). **Properties** **id** `string` Transaction ID. Use it with [Retrieve Transaction](/transactions/retrieve-transaction) to track status. **orgId** `string` Organization that owns the transaction. **vaultId** `string` Vault the transaction runs from. **status** `string` Lifecycle status. Track it until `COMPLETED`, `FAILED`, or `DECLINED`. See [TransactionStatus](/api-basics/data-models#transactionstatus). **transactionType** `string` See [TransactionType](/api-basics/data-models#transactiontype). **category** `string` For example `TRANSFER` or `CONTRACT_CALL`. See [TransactionCategory](/api-basics/data-models#transactioncategory). **subCategory** `string` See [TransactionSubCategory](/api-basics/data-models#transactionsubcategory). **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the transaction is deleted. **externalId** `string` Your own reference, if you set one. **memo** `string` Memo you attached, if any. **source** `TransferPartyData` Where the funds come from. Which fields are present depends on `type`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **destination** `TransferPartyData` Where the funds go. Same fields as `source`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **blockChain** `string` Chain the transaction runs on. **asset** `string` Asset moved. **amount** `string` Amount as a decimal string. **txHash** `string` On-chain transaction hash, once broadcast. **nextCursor** `string | null` Cursor for the next page. `null` on the last page. **hasNext** `boolean` Whether another page exists. ```json Request example theme={null} { "query": { "limit": 20, "cursor": null, "status": "COMPLETED", "vaultId": "vault_123" } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request GET \ --url "https://api.primevault.com/api/external/transactions/?limit=20&status=COMPLETED&vaultId=vault_123" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Accept: application/json" ``` ```typescript JavaScript SDK theme={null} getTransactions( params?: Record, limit?: number, cursor?: string | null, ): Promise ``` ```json Response example theme={null} { "results": [ { "id": "transaction_123", "orgId": "org_456", "vaultId": "vault_123", "status": "COMPLETED", "transactionType": "OUTGOING", "category": "TRANSFER", "subCategory": "EXTERNAL_TRANSFER", "createdAt": "2026-08-04T10:40:00Z", "updatedAt": "2026-08-04T10:42:15Z", "isDeleted": false, "externalId": "invoice-1001", "memo": "Vendor payout #1001", "source": { "type": "VAULT", "id": "vault_123", "subOrgId": "sub_org_123", "name": "Treasury Vault" }, "destination": { "type": "CONTACT", "id": "contact_456", "name": "Operations Wallet", "address": "0xAbC1234567890abcdef1234567890abcdef1234", "chain": "ETHEREUM" }, "blockChain": "ETHEREUM", "asset": "ETH", "amount": "0.50", "txHash": "0x9f4e6c1b7a2d3e4f", "balanceChanges": { "changes": [ { "party": { "type": "VAULT", "id": "vault_123", "subOrgId": "sub_org_123", "name": "Treasury Vault" }, "asset": "ETH", "amount": "-0.50", "chain": "ETHEREUM" }, { "party": { "type": "CONTACT", "id": "contact_456", "name": "Operations Wallet", "address": "0xAbC1234567890abcdef1234567890abcdef1234", "chain": "ETHEREUM" }, "asset": "ETH", "amount": "0.50", "chain": "ETHEREUM" } ] } } ], "nextCursor": "eyJpZCI6InRyYW5zYWN0aW9uXzEyMyJ9", "hasNext": true } ``` ```typescript JavaScript SDK theme={null} interface TransactionListResponse { results: Transaction[]; nextCursor?: string | null; hasNext?: boolean; } ``` # Create a Contract Call Source: https://docs.primevault.com/transactions/on-chain-operations/create-contract-call POST https://api.primevault.com/api/external/transactions/ Submit an arbitrary contract call from a vault, such as EVM call data, an ICP canister call, a raw-signing message, or an Alephium method. **POST** `/api/external/transactions/` Use a contract call for arbitrary contract execution. These transactions can have chain-specific data requirements and may require approval before broadcast. ## Request body **vaultId** `string` *required* Vault that signs and submits the call. **blockChain** `string` *required* Blockchain identifier. **amount** `string` Decimal amount of native asset sent with the call. **category** `string` *required* Must be `"CONTRACT_CALL"`. **data** `object` Chain-specific call payload. Use one of the following shapes. **Shapes** **EVM call data** `\{ callData: string; toAddress?: string \}` Encoded call data and the target contract address. **ICP canister call** `\{ canisterId: string; method: string; arg: string \}` Canister ID, method name, and encoded argument. **Raw signing** `\{ messageHex: string \}` Hex-encoded message to sign. **Alephium method** `\{ method: string; params: Record\ \}` Method name and parameters. **externalId** `string` Client reference. **gasParams** `object` Gas settings for the call. **Properties** **gasParams.feeTier** `string` One of `HIGH`, `MEDIUM`, or `LOW`. **creationOptions** `object` Creation options. **Properties** **creationOptions.skipPreprocessSimulation** `boolean` Set only when your integration has an explicit reason to bypass supported preprocessing or simulation. > [!NOTE] > > The SDK accepts `chain` and serializes it as `blockChain`. It injects `category: "CONTRACT_CALL"`. ## Response Returns the new [Transaction](/api-basics/data-models#transaction). Its `status` is the lifecycle source of truth. **id** `string` Transaction ID. Use it with [Retrieve Transaction](/transactions/retrieve-transaction) to track status. **orgId** `string` Organization that owns the transaction. **vaultId** `string` Vault the transaction runs from. **status** `string` Lifecycle status. Track it until `COMPLETED`, `FAILED`, or `DECLINED`. See [TransactionStatus](/api-basics/data-models#transactionstatus). **transactionType** `string` See [TransactionType](/api-basics/data-models#transactiontype). **category** `string` For example `TRANSFER` or `CONTRACT_CALL`. See [TransactionCategory](/api-basics/data-models#transactioncategory). **subCategory** `string` See [TransactionSubCategory](/api-basics/data-models#transactionsubcategory). **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the transaction is deleted. **externalId** `string` Your own reference, if you set one. **source** `TransferPartyData` Where the funds come from. Which fields are present depends on `type`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **blockChain** `string` Chain the transaction runs on. **amount** `string` Amount as a decimal string. ## Approve the transaction New transactions return `status: "PENDING"` and wait for approval. [Get the approval message](/approvals/get-approval-message) for the transaction `id`, sign it with your API user's key, then [submit the approval](/approvals/submit-approval-action). See [Approvals](/approvals/overview). With the SDK, call `approveChangeRequest({ entityId: transaction.id, action: "approve" })` after this call. ```json Request example theme={null} { "vaultId": "vault_123", "blockChain": "ETHEREUM", "amount": "0", "category": "CONTRACT_CALL", "data": { "callData": "0xa9059cbb000000000000000000000000...", "toAddress": "0xTokenContract1234567890abcdef" }, "externalId": "contract-call-1001", "gasParams": { "feeTier": "MEDIUM" }, "creationOptions": { "skipPreprocessSimulation": false } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/transactions/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "vaultId": "vault_123", "blockChain": "ETHEREUM", "amount": "0", "category": "CONTRACT_CALL", "data": { "callData": "0xa9059cbb000000000000000000000000...", "toAddress": "0xTokenContract1234567890abcdef" }, "externalId": "contract-call-1001", "gasParams": { "feeTier": "MEDIUM" }, "creationOptions": { "skipPreprocessSimulation": false } }' ``` ```typescript JavaScript SDK theme={null} const tx = await apiClient.createContractCallTransaction({ vaultId: "vault_123", chain: "ETHEREUM", amount: "0", data: { callData: "0xa9059cbb000000000000000000000000...", toAddress: "0xTokenContract1234567890abcdef", }, externalId: "contract-call-1001", gasParams: { feeTier: "MEDIUM" }, creationOptions: { skipPreprocessSimulation: false }, }); console.log(tx.id, tx.status); ``` ```json Response example theme={null} { "id": "transaction_contract_123", "orgId": "org_456", "vaultId": "vault_123", "status": "PENDING", "transactionType": "OUTGOING", "category": "CONTRACT_CALL", "subCategory": "CONTRACT_CALL", "createdAt": "2026-08-04T11:00:00Z", "updatedAt": "2026-08-04T11:00:00Z", "isDeleted": false, "externalId": "contract-call-1001", "source": { "type": "VAULT", "id": "vault_123", "name": "Treasury Vault" }, "blockChain": "ETHEREUM", "amount": "0" } ``` ```typescript JavaScript SDK theme={null} interface Transaction { id: string; orgId: string; vaultId: string; status: TransactionStatus | string; transactionType: TransactionType | string; category: TransactionCategory | string; subCategory: TransactionSubCategory | string; createdAt: string; updatedAt: string; isDeleted: boolean; amount: string; txHash?: string; error?: string; externalId?: string; createdById?: string; fees?: Fees; memo?: string; txnSignature?: string; txnSignatureData?: Record; output?: TransactionOutput; amountInUSD?: string; nonce?: number; dAppId?: string; source?: TransferPartyData; destination?: TransferPartyData; quoteResponse?: QuoteResponseItem; depositInstructions?: DepositInstructions; operations?: TransactionOperation[]; balanceChanges?: TransactionOperationBalanceChanges | null; blockChain?: string; asset?: string; /** Deprecated: use destination.address. */ toAddress?: string; /** Deprecated: use destination.name. */ toAddressName?: string; /** Deprecated: use destination.id. */ toVaultId?: string; /** Deprecated: use source.address. */ sourceAddress?: string; } ``` # Create a Transfer Source: https://docs.primevault.com/transactions/on-chain-operations/create-transfer POST https://api.primevault.com/api/external/transactions/ Send assets from a source to a destination. **POST** `/api/external/transactions/` Use a transfer when you already know the source, destination, blockchain, asset, and amount. You can [estimate the network fee](/transactions/estimate-transfer-fee) first. The returned `status` is the lifecycle source of truth. ## Request body **source** `TransferPartyData` *required* Sending party. **destination** `TransferPartyData` *required* Receiving party. **amount** `string` *required* Decimal transfer amount. **asset** `string` *required* Asset symbol. **blockChain** `string` *required* Blockchain identifier. **category** `string` *required* Must be `"TRANSFER"`. **externalId** `string` Client reference. **memo** `string` Human-readable note. **feePayer** `object` Vault that pays the network fee when the chain supports sponsored fee payment. **Properties** **feePayer.id** `string` *required* Vault ID that will pay the fee. > [!NOTE] > > The SDK accepts `chain` and serializes it as `blockChain`. It injects `category: "TRANSFER"`. ## Response Returns the new [Transaction](/api-basics/data-models#transaction). Its `status` is the lifecycle source of truth. **id** `string` Transaction ID. Use it with [Retrieve Transaction](/transactions/retrieve-transaction) to track status. **orgId** `string` Organization that owns the transaction. **vaultId** `string` Vault the transaction runs from. **status** `string` Lifecycle status. Track it until `COMPLETED`, `FAILED`, or `DECLINED`. See [TransactionStatus](/api-basics/data-models#transactionstatus). **transactionType** `string` See [TransactionType](/api-basics/data-models#transactiontype). **category** `string` For example `TRANSFER` or `CONTRACT_CALL`. See [TransactionCategory](/api-basics/data-models#transactioncategory). **subCategory** `string` See [TransactionSubCategory](/api-basics/data-models#transactionsubcategory). **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the transaction is deleted. **externalId** `string` Your own reference, if you set one. **memo** `string` Memo you attached, if any. **source** `TransferPartyData` Where the funds come from. Which fields are present depends on `type`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **destination** `TransferPartyData` Where the funds go. Same fields as `source`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **blockChain** `string` Chain the transaction runs on. **asset** `string` Asset moved. **amount** `string` Amount as a decimal string. ## Approve the transaction New transfers return `status: "PENDING"` and wait for approval. [Get the approval message](/approvals/get-approval-message) for the transaction `id`, sign it with your API user's key, then [submit the approval](/approvals/submit-approval-action). See [Approvals](/approvals/overview). With the SDK, `createTransactionWithApproval(request)` creates and approves in one call. `createTransferTransaction(request)` only creates. ```json Request example theme={null} { "source": { "type": "VAULT", "id": "vault_123" }, "destination": { "type": "CONTACT", "id": "contact_456" }, "amount": "0.50", "asset": "ETH", "blockChain": "ETHEREUM", "category": "TRANSFER", "externalId": "invoice-1001", "memo": "Vendor payout #1001", "feePayer": { "id": "vault_234" } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/transactions/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "source": { "type": "VAULT", "id": "vault_123" }, "destination": { "type": "CONTACT", "id": "contact_456" }, "amount": "0.50", "asset": "ETH", "blockChain": "ETHEREUM", "category": "TRANSFER", "externalId": "invoice-1001", "memo": "Vendor payout #1001", "feePayer": { "id": "vault_234" } }' ``` ```typescript JavaScript SDK theme={null} interface CreateTransferTransactionRequest { source: TransferPartyData; destination: TransferPartyData; amount: string; asset: string; chain: string; externalId?: string; memo?: string; feePayer?: { id: string }; } createTransferTransaction( request: CreateTransferTransactionRequest, ): Promise const tx = await apiClient.createTransferTransaction({ source: { type: "VAULT", id: "vault_123" }, destination: { type: "CONTACT", id: "contact_456" }, amount: "0.50", asset: "ETH", chain: "ETHEREUM", externalId: "invoice-1001", memo: "Vendor payout #1001", feePayer: { id: "vault_234" }, }); console.log(tx.id, tx.status); ``` ```json Response example theme={null} { "id": "transaction_123", "orgId": "org_456", "vaultId": "vault_123", "status": "PENDING", "transactionType": "OUTGOING", "category": "TRANSFER", "subCategory": "EXTERNAL_TRANSFER", "createdAt": "2026-08-04T10:40:00Z", "updatedAt": "2026-08-04T10:40:00Z", "isDeleted": false, "externalId": "invoice-1001", "memo": "Vendor payout #1001", "source": { "type": "VAULT", "id": "vault_123", "subOrgId": "sub_org_123", "name": "Treasury Vault" }, "destination": { "type": "CONTACT", "id": "contact_456", "name": "Operations Wallet", "address": "0xAbC1234567890abcdef1234567890abcdef1234", "chain": "ETHEREUM" }, "blockChain": "ETHEREUM", "asset": "ETH", "amount": "0.50" } ``` ```typescript JavaScript SDK theme={null} interface Transaction { id: string; orgId: string; vaultId: string; status: TransactionStatus | string; transactionType: TransactionType | string; category: TransactionCategory | string; subCategory: TransactionSubCategory | string; createdAt: string; updatedAt: string; isDeleted: boolean; amount: string; txHash?: string; error?: string; externalId?: string; createdById?: string; fees?: Fees; memo?: string; txnSignature?: string; txnSignatureData?: Record; output?: TransactionOutput; amountInUSD?: string; nonce?: number; dAppId?: string; source?: TransferPartyData; destination?: TransferPartyData; quoteResponse?: QuoteResponseItem; depositInstructions?: DepositInstructions; operations?: TransactionOperation[]; balanceChanges?: TransactionOperationBalanceChanges | null; blockChain?: string; asset?: string; /** Deprecated: use destination.address. */ toAddress?: string; /** Deprecated: use destination.name. */ toAddressName?: string; /** Deprecated: use destination.id. */ toVaultId?: string; /** Deprecated: use source.address. */ sourceAddress?: string; } ``` # Delegate a TRON Resource Source: https://docs.primevault.com/transactions/on-chain-operations/delegate-resource POST https://api.primevault.com/api/external/transactions/ Delegate TRON energy or bandwidth from a vault to a destination party, such as a contact, and get back a pending transaction. **POST** `/api/external/transactions/` Use resource delegation for chain-level protocol actions. These transactions can have chain-specific data requirements and may require approval before broadcast. ## Request body **source** `TransferPartyData` *required* Vault that delegates the resource. **destination** `TransferPartyData` *required* Party that receives the delegated resource. **asset** `string` *required* Asset symbol. **blockChain** `string` *required* Blockchain identifier. **amount** `string` *required* Decimal amount to delegate. **resourceType** `string` *required* One of `TRON_ENERGY` or `TRON_BANDWIDTH`. **category** `string` *required* Must be `"DELEGATE_RESOURCE"`. **externalId** `string` Client reference. **memo** `string` Human-readable note. > [!NOTE] > > The SDK accepts `chain` and serializes it as `blockChain`. It injects `category: "DELEGATE_RESOURCE"`. ## Response Returns the new [Transaction](/api-basics/data-models#transaction). Its `status` is the lifecycle source of truth. **id** `string` Transaction ID. Use it with [Retrieve Transaction](/transactions/retrieve-transaction) to track status. **orgId** `string` Organization that owns the transaction. **vaultId** `string` Vault the transaction runs from. **status** `string` Lifecycle status. Track it until `COMPLETED`, `FAILED`, or `DECLINED`. See [TransactionStatus](/api-basics/data-models#transactionstatus). **transactionType** `string` See [TransactionType](/api-basics/data-models#transactiontype). **category** `string` For example `TRANSFER` or `CONTRACT_CALL`. See [TransactionCategory](/api-basics/data-models#transactioncategory). **subCategory** `string` See [TransactionSubCategory](/api-basics/data-models#transactionsubcategory). **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the transaction is deleted. **externalId** `string` Your own reference, if you set one. **memo** `string` Memo you attached, if any. **source** `TransferPartyData` Where the funds come from. Which fields are present depends on `type`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **destination** `TransferPartyData` Where the funds go. Same fields as `source`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **blockChain** `string` Chain the transaction runs on. **asset** `string` Asset moved. **amount** `string` Amount as a decimal string. ## Approve the transaction New transactions return `status: "PENDING"` and wait for approval. [Get the approval message](/approvals/get-approval-message) for the transaction `id`, sign it with your API user's key, then [submit the approval](/approvals/submit-approval-action). See [Approvals](/approvals/overview). With the SDK, call `approveChangeRequest({ entityId: transaction.id, action: "approve" })` after this call. ```json Request example theme={null} { "source": { "type": "VAULT", "id": "vault_123" }, "destination": { "type": "CONTACT", "id": "contact_456" }, "asset": "TRX", "blockChain": "TRON", "amount": "500", "resourceType": "TRON_ENERGY", "category": "DELEGATE_RESOURCE", "externalId": "delegate-1001", "memo": "Delegate energy to operations" } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/transactions/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "source": { "type": "VAULT", "id": "vault_123" }, "destination": { "type": "CONTACT", "id": "contact_456" }, "asset": "TRX", "blockChain": "TRON", "amount": "500", "resourceType": "TRON_ENERGY", "category": "DELEGATE_RESOURCE", "externalId": "delegate-1001", "memo": "Delegate energy to operations" }' ``` ```typescript JavaScript SDK theme={null} const tx = await apiClient.delegateResource({ source: { type: "VAULT", id: "vault_123" }, destination: { type: "CONTACT", id: "contact_456" }, asset: "TRX", chain: "TRON", amount: "500", resourceType: "TRON_ENERGY", externalId: "delegate-1001", memo: "Delegate energy to operations", }); console.log(tx.id, tx.status); ``` ```json Response example theme={null} { "id": "transaction_delegate_123", "orgId": "org_456", "vaultId": "vault_123", "status": "PENDING", "transactionType": "OUTGOING", "category": "DELEGATE_RESOURCE", "subCategory": "DELEGATE_RESOURCE", "createdAt": "2026-08-04T11:50:00Z", "updatedAt": "2026-08-04T11:50:00Z", "isDeleted": false, "externalId": "delegate-1001", "memo": "Delegate energy to operations", "source": { "type": "VAULT", "id": "vault_123", "name": "Treasury Vault" }, "destination": { "type": "CONTACT", "id": "contact_456", "name": "Operations Wallet" }, "blockChain": "TRON", "asset": "TRX", "amount": "500" } ``` ```typescript JavaScript SDK theme={null} interface Transaction { id: string; orgId: string; vaultId: string; status: TransactionStatus | string; transactionType: TransactionType | string; category: TransactionCategory | string; subCategory: TransactionSubCategory | string; createdAt: string; updatedAt: string; isDeleted: boolean; amount: string; txHash?: string; error?: string; externalId?: string; createdById?: string; fees?: Fees; memo?: string; txnSignature?: string; txnSignatureData?: Record; output?: TransactionOutput; amountInUSD?: string; nonce?: number; dAppId?: string; source?: TransferPartyData; destination?: TransferPartyData; quoteResponse?: QuoteResponseItem; depositInstructions?: DepositInstructions; operations?: TransactionOperation[]; balanceChanges?: TransactionOperationBalanceChanges | null; blockChain?: string; asset?: string; /** Deprecated: use destination.address. */ toAddress?: string; /** Deprecated: use destination.name. */ toAddressName?: string; /** Deprecated: use destination.id. */ toVaultId?: string; /** Deprecated: use source.address. */ sourceAddress?: string; } ``` # Replace a Transaction Source: https://docs.primevault.com/transactions/on-chain-operations/replace-transaction POST https://api.primevault.com/api/external/transactions/replace_transaction/ Replace a submitted transaction on a supported chain, if it doesn't already have an active replacement. **POST** `/api/external/transactions/replace_transaction/` > [!NOTE] > > Replacement is accepted only for a SUBMITTED transaction on a chain that supports replacement and when no active replacement already exists. ## Request body **transactionId** `string` *required* The ID of the transaction to replace. ## Response Returns the replacement [Transaction](/api-basics/data-models#transaction). Its `status` is the lifecycle source of truth. **id** `string` Transaction ID. Use it with [Retrieve Transaction](/transactions/retrieve-transaction) to track status. **orgId** `string` Organization that owns the transaction. **vaultId** `string` Vault the transaction runs from. **status** `string` Lifecycle status. Track it until `COMPLETED`, `FAILED`, or `DECLINED`. See [TransactionStatus](/api-basics/data-models#transactionstatus). **transactionType** `string` See [TransactionType](/api-basics/data-models#transactiontype). **category** `string` For example `TRANSFER` or `CONTRACT_CALL`. See [TransactionCategory](/api-basics/data-models#transactioncategory). **subCategory** `string` See [TransactionSubCategory](/api-basics/data-models#transactionsubcategory). **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the transaction is deleted. **externalId** `string` Your own reference, if you set one. **memo** `string` Memo you attached, if any. **source** `TransferPartyData` Where the funds come from. Which fields are present depends on `type`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **destination** `TransferPartyData` Where the funds go. Same fields as `source`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **blockChain** `string` Chain the transaction runs on. **asset** `string` Asset moved. **amount** `string` Amount as a decimal string. **nonce** `number` Chain nonce of the transaction. ## Approve the transaction New transactions return `status: "PENDING"` and wait for approval. [Get the approval message](/approvals/get-approval-message) for the transaction `id`, sign it with your API user's key, then [submit the approval](/approvals/submit-approval-action). See [Approvals](/approvals/overview). With the SDK, call `approveChangeRequest({ entityId: transaction.id, action: "approve" })` after this call. ```json Request example theme={null} { "transactionId": "transaction_123" } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/transactions/replace_transaction/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --header "Accept: application/json" \ --data '{"transactionId": "transaction_123"}' ``` ```typescript JavaScript SDK theme={null} replaceTransaction(request: { transactionId: string; }): Promise ``` ```json Response example theme={null} { "id": "transaction_replacement_456", "orgId": "org_456", "vaultId": "vault_123", "status": "PENDING", "transactionType": "OUTGOING", "category": "TRANSFER", "subCategory": "EXTERNAL_TRANSFER", "createdAt": "2026-08-04T10:40:00Z", "updatedAt": "2026-08-04T11:30:00Z", "isDeleted": false, "externalId": "invoice-1001-replacement", "memo": "Vendor payout #1001", "source": { "type": "VAULT", "id": "vault_123", "subOrgId": "sub_org_123", "name": "Treasury Vault" }, "destination": { "type": "CONTACT", "id": "contact_456", "name": "Operations Wallet", "address": "0xAbC1234567890abcdef1234567890abcdef1234", "chain": "ETHEREUM" }, "blockChain": "ETHEREUM", "asset": "ETH", "amount": "0.50", "nonce": 42 } ``` ```typescript JavaScript SDK theme={null} interface Transaction { id: string; orgId: string; vaultId: string; status: TransactionStatus | string; transactionType: TransactionType | string; category: TransactionCategory | string; subCategory: TransactionSubCategory | string; createdAt: string; updatedAt: string; isDeleted: boolean; amount: string; txHash?: string; error?: string; externalId?: string; createdById?: string; fees?: Fees; memo?: string; txnSignature?: string; txnSignatureData?: Record; output?: TransactionOutput; amountInUSD?: string; nonce?: number; dAppId?: string; source?: TransferPartyData; destination?: TransferPartyData; quoteResponse?: QuoteResponseItem; depositInstructions?: DepositInstructions; operations?: TransactionOperation[]; balanceChanges?: TransactionOperationBalanceChanges | null; blockChain?: string; asset?: string; /** Deprecated: use destination.address. */ toAddress?: string; /** Deprecated: use destination.name. */ toAddressName?: string; /** Deprecated: use destination.id. */ toVaultId?: string; /** Deprecated: use source.address. */ sourceAddress?: string; } ``` # Stake a Resource Source: https://docs.primevault.com/transactions/on-chain-operations/stake-resource POST https://api.primevault.com/api/external/transactions/ Stake an asset from a vault to obtain a chain resource, such as staking TRX on TRON for energy or bandwidth. **POST** `/api/external/transactions/` Use staking for chain-level protocol actions. These transactions can have chain-specific data requirements and may require approval before broadcast. ## Request body **source** `TransferPartyData` *required* Vault that stakes the asset. **asset** `string` *required* Asset symbol. **blockChain** `string` *required* Blockchain identifier. **amount** `string` *required* Decimal amount to stake. **resourceType** `string` One of `TRON_ENERGY` or `TRON_BANDWIDTH`. **category** `string` *required* Must be `"STAKE"`. **externalId** `string` Client reference. **memo** `string` Human-readable note. > [!NOTE] > > The SDK accepts `chain` and serializes it as `blockChain`. It injects `category: "STAKE"`. ## Response Returns the new [Transaction](/api-basics/data-models#transaction). Its `status` is the lifecycle source of truth. **id** `string` Transaction ID. Use it with [Retrieve Transaction](/transactions/retrieve-transaction) to track status. **orgId** `string` Organization that owns the transaction. **vaultId** `string` Vault the transaction runs from. **status** `string` Lifecycle status. Track it until `COMPLETED`, `FAILED`, or `DECLINED`. See [TransactionStatus](/api-basics/data-models#transactionstatus). **transactionType** `string` See [TransactionType](/api-basics/data-models#transactiontype). **category** `string` For example `TRANSFER` or `CONTRACT_CALL`. See [TransactionCategory](/api-basics/data-models#transactioncategory). **subCategory** `string` See [TransactionSubCategory](/api-basics/data-models#transactionsubcategory). **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the transaction is deleted. **externalId** `string` Your own reference, if you set one. **memo** `string` Memo you attached, if any. **source** `TransferPartyData` Where the funds come from. Which fields are present depends on `type`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **blockChain** `string` Chain the transaction runs on. **asset** `string` Asset moved. **amount** `string` Amount as a decimal string. ## Approve the transaction New transactions return `status: "PENDING"` and wait for approval. [Get the approval message](/approvals/get-approval-message) for the transaction `id`, sign it with your API user's key, then [submit the approval](/approvals/submit-approval-action). See [Approvals](/approvals/overview). With the SDK, call `approveChangeRequest({ entityId: transaction.id, action: "approve" })` after this call. ```json Request example theme={null} { "source": { "type": "VAULT", "id": "vault_123" }, "asset": "TRX", "blockChain": "TRON", "amount": "1000", "resourceType": "TRON_ENERGY", "category": "STAKE", "externalId": "stake-1001", "memo": "Stake TRX for energy" } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/transactions/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "source": { "type": "VAULT", "id": "vault_123" }, "asset": "TRX", "blockChain": "TRON", "amount": "1000", "resourceType": "TRON_ENERGY", "category": "STAKE", "externalId": "stake-1001", "memo": "Stake TRX for energy" }' ``` ```typescript JavaScript SDK theme={null} const tx = await apiClient.stakeResource({ source: { type: "VAULT", id: "vault_123" }, asset: "TRX", chain: "TRON", amount: "1000", resourceType: "TRON_ENERGY", externalId: "stake-1001", memo: "Stake TRX for energy", }); console.log(tx.id, tx.status); ``` ```json Response example theme={null} { "id": "transaction_stake_123", "orgId": "org_456", "vaultId": "vault_123", "status": "PENDING", "transactionType": "OUTGOING", "category": "STAKE", "subCategory": "STAKE", "createdAt": "2026-08-04T11:40:00Z", "updatedAt": "2026-08-04T11:40:00Z", "isDeleted": false, "externalId": "stake-1001", "memo": "Stake TRX for energy", "source": { "type": "VAULT", "id": "vault_123", "name": "Treasury Vault" }, "blockChain": "TRON", "asset": "TRX", "amount": "1000" } ``` ```typescript JavaScript SDK theme={null} interface Transaction { id: string; orgId: string; vaultId: string; status: TransactionStatus | string; transactionType: TransactionType | string; category: TransactionCategory | string; subCategory: TransactionSubCategory | string; createdAt: string; updatedAt: string; isDeleted: boolean; amount: string; txHash?: string; error?: string; externalId?: string; createdById?: string; fees?: Fees; memo?: string; txnSignature?: string; txnSignatureData?: Record; output?: TransactionOutput; amountInUSD?: string; nonce?: number; dAppId?: string; source?: TransferPartyData; destination?: TransferPartyData; quoteResponse?: QuoteResponseItem; depositInstructions?: DepositInstructions; operations?: TransactionOperation[]; balanceChanges?: TransactionOperationBalanceChanges | null; blockChain?: string; asset?: string; /** Deprecated: use destination.address. */ toAddress?: string; /** Deprecated: use destination.name. */ toAddressName?: string; /** Deprecated: use destination.id. */ toVaultId?: string; /** Deprecated: use source.address. */ sourceAddress?: string; } ``` # Transactions Source: https://docs.primevault.com/transactions/overview Track transfers, on-chain operations, ramps, FX, and trades through the transaction lifecycle. Transactions are the lifecycle record for transfers, contract calls, staking and delegation, ramps, FX, and trades. All amounts are decimal strings. Create calls return a Transaction; its status is the lifecycle source of truth. A successful HTTP response does not imply that the transaction has completed. Every create call returns a transaction in `PENDING` that waits for your approval before PrimeVault processes it. See [Approvals](/approvals/overview). ## Choose a transaction flow | Goal | Flow | | - | - | | Send an asset from a known source to a known destination | [**Create Transfer**](/transactions/on-chain-operations/create-transfer) | | Submit a contract call from a vault, such as EVM call data | [**Create Contract Call**](/transactions/on-chain-operations/create-contract-call) | | Stake an asset for a chain resource, such as TRX for TRON energy | [**Stake Resource**](/transactions/on-chain-operations/stake-resource) | | Delegate TRON energy or bandwidth to another party | [**Delegate Resource**](/transactions/on-chain-operations/delegate-resource) | | Convert fiat to crypto (on-ramp) or crypto to fiat (off-ramp) | [**Ramps**](/transactions/ramps) | | Convert one fiat currency to another, such as NGN to USD | [**FX**](/transactions/fx) | | Buy or sell stablecoins against fiat with a liquidity provider | [**Trade**](/transactions/trade) | ## Shared transaction operations > [card] [List transactions](/transactions/list-transactions) > [card] [Retrieve a transaction](/transactions/retrieve-transaction) > [card] [Replace a submitted transaction](/transactions/on-chain-operations/replace-transaction) > [card] [Approve a transaction](/approvals/overview) # Ramps Source: https://docs.primevault.com/transactions/ramps Ramps convert fiat to crypto (on-ramp) or crypto to fiat (off-ramp). Request a quote, execute it, then track the transaction to completion. Ramps convert fiat to crypto (on-ramp) or crypto to fiat (off-ramp), such as USD to USDC or USDC to USD. To exchange one fiat currency for another, see [FX](/transactions/fx). ## API flow **1. Get a quote** Describe the source, destination, assets, and exactly one amount. Save the `quoteId` of the quote you choose. See [Get Quote](/transactions/ramps/get-quote). **2. Execute the quote** Create a transaction from the `quoteId`. See [Execute Quote](/transactions/ramps/execute-quote). **3. Approve the transaction** The new transaction is `PENDING` until approved. [Get the approval message](/approvals/get-approval-message), sign it, and [submit the approval](/approvals/submit-approval-action). The SDK's `createTransactionFromIntent()` does this for you. See [Approvals](/approvals/overview). **4. Track the transaction** Use [Retrieve Transaction](/transactions/retrieve-transaction) or [webhooks](/api-basics/webhooks) to track it until it reaches `COMPLETED`, `FAILED`, or `DECLINED`. > [card] [Get Quote](/transactions/ramps/get-quote) > [card] [Execute Quote](/transactions/ramps/execute-quote) # Execute a Ramp Quote Source: https://docs.primevault.com/transactions/ramps/execute-quote POST https://api.primevault.com/api/external/transactions/intent/create/ Create a ramp transaction from a selected quoteId. Returns HTTP 201 with the full Transaction. **POST** `/api/external/transactions/intent/create/` **Example:** executes the off-ramp quote `quote_ramp_payroll_1042`, converting 10,000 USDC into 9,975 USD (after a 25 USDC fee) for a payroll bank account. ## Request body **quoteId** `string` *required* The `quoteId` returned by [Get Quote](/transactions/ramps/get-quote). **intent** `null` Send `null` when executing a quote. Do not repeat the quote intent. **externalId** `string` Client reference. **memo** `string` Human-readable note. ## Response Returns HTTP 201 with a full [Transaction](/api-basics/data-models#transaction). Optional fields depend on the payment route and status. **id** `string` Transaction ID. Use it with [Retrieve Transaction](/transactions/retrieve-transaction) to track status. **status** `string` Lifecycle status. Track it until `COMPLETED`, `FAILED`, or `DECLINED`. **quoteResponse** `QuoteResponseItem` The executed quote, including its `input`, `output`, `source`, `destination`, and `expiresAt`. See [QuoteResponseItem](/api-basics/data-models#quoteresponseitem). **source** `TransferPartyData` Where the funds come from, as a [TransferPartyData](/api-basics/data-models#transferpartydata). Which fields are present depends on `type`. **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **destination** `TransferPartyData` Where the funds go. Same fields as `source`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **balanceChanges** `TransactionOperationBalanceChanges | null` Aggregate balance movements across the transaction, when present: the input asset debited and the output asset credited. See [TransactionOperationBalanceChanges](/api-basics/data-models#transactionoperationbalancechanges). **Properties** **changes** `TransactionOperationBalanceChange[]` One entry per party and asset. See [TransactionOperationBalanceChange](/api-basics/data-models#transactionoperationbalancechange). **Properties** **party** `TransferPartyData | null` Party whose balance changed. Same fields as `source`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **asset** `string` Asset symbol. **amount** `string` Signed decimal string: negative for a debit, positive for a credit. **chain** `string` Blockchain, for crypto movements. **paymentRail** `string` Payment rail, for fiat movements. **depositInstructions** `DepositInstructions` Present when funding is required. Use the returned details exactly. See [DepositInstructions](/api-basics/data-models#depositinstructions). > [!NOTE] > > Executing a quote creates a transaction in `PENDING` that waits for approval. [Get the approval message](/approvals/get-approval-message) for the transaction `id`, sign it, then [submit the approval](/approvals/submit-approval-action). The SDK's `createTransactionFromIntent()` does this for you and returns the updated transaction. See [Approvals](/approvals/overview). ```json Request example theme={null} { "intent": null, "quoteId": "quote_ramp_payroll_1042", "externalId": "payroll-top-up-1042", "memo": "Convert 10000 USDC to USD for payroll" } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/transactions/intent/create/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "intent": null, "quoteId": "quote_ramp_payroll_1042", "externalId": "payroll-top-up-1042", "memo": "Convert 10000 USDC to USD for payroll" }' ``` ```typescript JavaScript SDK theme={null} const transaction = await apiClient.createTransactionFromIntent({ quoteId: "quote_ramp_payroll_1042", externalId: "payroll-top-up-1042", memo: "Convert 10000 USDC to USD for payroll", }); console.log(transaction.id, transaction.status); ``` ```json Response example theme={null} { "id": "transaction_ramp_payroll_1042", "orgId": "org_456", "vaultId": "vault_usdc_treasury", "status": "PENDING", "transactionType": "OUTGOING", "category": "RAMP", "subCategory": "OFF_RAMP", "createdAt": "2026-08-04T10:40:00Z", "updatedAt": "2026-08-04T10:40:00Z", "isDeleted": false, "externalId": "payroll-top-up-1042", "memo": "Convert 10000 USDC to USD for payroll", "blockChain": "ETHEREUM", "source": { "type": "VAULT", "id": "vault_usdc_treasury", "chain": "ETHEREUM" }, "destination": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_payroll", "paymentRail": "ACH" }, "balanceChanges": { "changes": [ { "party": { "type": "VAULT", "id": "vault_usdc_treasury" }, "asset": "USDC", "amount": "-10000.00", "chain": "ETHEREUM" }, { "party": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_payroll" }, "asset": "USD", "amount": "9975.00", "paymentRail": "ACH" } ] }, "fees": { "amount": "25.00", "asset": "USDC" }, "quoteResponse": { "quoteId": "quote_ramp_payroll_1042", "rate": "1", "fees": { "amount": "25.00", "asset": "USDC" }, "input": { "asset": "USDC", "amount": "10000.00" }, "output": { "asset": "USD", "amount": "9975.00", "vaultId": "vault_usd_settlement" }, "source": { "type": "VAULT", "id": "vault_usdc_treasury", "chain": "ETHEREUM" }, "destination": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_payroll", "paymentRail": "ACH" }, "expiresAt": "2026-09-30T10:00:30+00:00" } } ``` ```typescript JavaScript SDK theme={null} interface Transaction { id: string; orgId: string; vaultId: string; status: TransactionStatus | string; transactionType: TransactionType | string; category: TransactionCategory | string; subCategory: TransactionSubCategory | string; createdAt: string; updatedAt: string; isDeleted: boolean; amount: string; txHash?: string; error?: string; externalId?: string; createdById?: string; fees?: Fees; memo?: string; txnSignature?: string; txnSignatureData?: Record; output?: TransactionOutput; amountInUSD?: string; nonce?: number; dAppId?: string; source?: TransferPartyData; destination?: TransferPartyData; quoteResponse?: QuoteResponseItem; depositInstructions?: DepositInstructions; operations?: TransactionOperation[]; balanceChanges?: TransactionOperationBalanceChanges | null; blockChain?: string; asset?: string; /** Deprecated: use destination.address. */ toAddress?: string; /** Deprecated: use destination.name. */ toAddressName?: string; /** Deprecated: use destination.id. */ toVaultId?: string; /** Deprecated: use source.address. */ sourceAddress?: string; } ``` # Get a Ramp Quote Source: https://docs.primevault.com/transactions/ramps/get-quote POST https://api.primevault.com/api/external/transactions/v2/quote/ Request priced quotes for an on-ramp or off-ramp by describing the source, destination, assets, and exactly one amount. **POST** `/api/external/transactions/v2/quote/` **Example:** an off-ramp quote to sell 10,000 USDC from an Ethereum treasury vault and receive USD in a payroll bank account over ACH. ## Request body **intent** `TransactionIntentRequest` *required* Quote criteria for the requested ramp. See [TransactionIntentRequest](/api-basics/data-models#transactionintentrequest). **Properties** **intent.input** `IntentAsset` *required* What you spend. See [IntentAsset](/api-basics/data-models#intentasset). **Properties** **intent.input.asset** `string` *required* Asset symbol, such as `USDC` or `USD`. **intent.input.amount** `string` Decimal amount to spend. Provide either this or `intent.output.amount`, not both. **intent.input.vaultId** `string` Vault or account that holds the input asset. **intent.output** `IntentAsset` *required* What you receive. See [IntentAsset](/api-basics/data-models#intentasset). **Properties** **intent.output.asset** `string` *required* Asset symbol, such as `USD` or `USDC`. **intent.output.amount** `string` Decimal amount to receive. Provide either this or `intent.input.amount`, not both. **intent.output.vaultId** `string` Vault or account that receives the output asset. **intent.source** `TransferPartyData` *required* Source endpoint. Set `chain` for a crypto leg or `paymentRail` for a fiat leg. See [TransferPartyData](/api-basics/data-models#transferpartydata). **intent.destination** `TransferPartyData` *required* Destination endpoint. Set `chain` for a crypto leg or `paymentRail` for a fiat leg. See [TransferPartyData](/api-basics/data-models#transferpartydata). > [!NOTE] > > Supply both assets and exactly one non-null amount, as a decimal string. Omit the other amount. ## Response **quotes** `QuoteResponseItem[]` Available quotes for the intent. See [QuoteResponseItem](/api-basics/data-models#quoteresponseitem). **Properties** **quoteId** `string` Quote identifier. Pass it to [Execute Quote](/transactions/ramps/execute-quote). **rate** `string | null` Quoted conversion rate. **fees** `Fees | null` Fee `amount`, `asset`, and optional `amountInFiat`. See [Fees](/api-basics/data-models#fees). **input** `IntentAsset` Quoted input asset and amount. See [IntentAsset](/api-basics/data-models#intentasset). **output** `IntentAsset` Quoted output asset and amount. See [IntentAsset](/api-basics/data-models#intentasset). **source** `TransferPartyData` Source endpoint for the ramp. See [TransferPartyData](/api-basics/data-models#transferpartydata). **destination** `TransferPartyData` Destination endpoint for the ramp. See [TransferPartyData](/api-basics/data-models#transferpartydata). **expiresAt** `string` ISO 8601 time when the quote expires. Execute the quote before this time. ```json Request example theme={null} { "intent": { "input": { "asset": "USDC", "amount": "10000.00" }, "output": { "asset": "USD", "vaultId": "vault_usd_settlement" }, "source": { "type": "VAULT", "id": "vault_usdc_treasury", "chain": "ETHEREUM" }, "destination": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_payroll", "paymentRail": "ACH" } } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/transactions/v2/quote/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "intent": { "input": { "asset": "USDC", "amount": "10000.00" }, "output": { "asset": "USD", "vaultId": "vault_usd_settlement" }, "source": { "type": "VAULT", "id": "vault_usdc_treasury", "chain": "ETHEREUM" }, "destination": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_payroll", "paymentRail": "ACH" } } }' ``` ```typescript JavaScript SDK theme={null} const quoteResponse = await apiClient.getQuote({ intent: { input: { asset: "USDC", amount: "10000.00" }, output: { asset: "USD", vaultId: "vault_usd_settlement" }, source: { type: "VAULT", id: "vault_usdc_treasury", chain: "ETHEREUM" }, destination: { type: "BANK_ACCOUNT", id: "bank_account_usd_payroll", paymentRail: "ACH" }, }, }); console.log(quoteResponse.quotes); ``` ```json Response example theme={null} { "quotes": [ { "quoteId": "quote_ramp_payroll_1042", "rate": "1", "fees": { "amount": "25.00", "asset": "USDC" }, "input": { "asset": "USDC", "amount": "10000.00" }, "output": { "asset": "USD", "amount": "9975.00", "vaultId": "vault_usd_settlement" }, "source": { "type": "VAULT", "id": "vault_usdc_treasury", "chain": "ETHEREUM" }, "destination": { "type": "BANK_ACCOUNT", "id": "bank_account_usd_payroll", "paymentRail": "ACH" }, "expiresAt": "2026-09-30T10:00:30+00:00" } ] } ``` ```typescript JavaScript SDK theme={null} interface QuoteResponse { quotes: QuoteResponseItem[]; } interface QuoteResponseItem { quoteId: string; rate?: string | null; fees?: Fees | null; input?: IntentAsset; output?: IntentAsset; source?: TransferPartyData; destination?: TransferPartyData; expiresAt?: string; } ``` # Retrieve a Transaction Source: https://docs.primevault.com/transactions/retrieve-transaction GET https://api.primevault.com/api/external/transactions/{transactionId}/ Retrieve a single transaction by ID, including its balance changes and status. **GET** `/api/external/transactions/{transactionId}/` ## Path parameter **transactionId** `string` *required* ID returned by a transaction create or list response. ## Response Returns the [Transaction](/api-basics/data-models#transaction). Its `status` is the lifecycle source of truth. **id** `string` Transaction ID. Use it with [Retrieve Transaction](/transactions/retrieve-transaction) to track status. **orgId** `string` Organization that owns the transaction. **vaultId** `string` Vault the transaction runs from. **status** `string` Lifecycle status. Track it until `COMPLETED`, `FAILED`, or `DECLINED`. See [TransactionStatus](/api-basics/data-models#transactionstatus). **transactionType** `string` See [TransactionType](/api-basics/data-models#transactiontype). **category** `string` For example `TRANSFER` or `CONTRACT_CALL`. See [TransactionCategory](/api-basics/data-models#transactioncategory). **subCategory** `string` See [TransactionSubCategory](/api-basics/data-models#transactionsubcategory). **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the transaction is deleted. **externalId** `string` Your own reference, if you set one. **memo** `string` Memo you attached, if any. **source** `TransferPartyData` Where the funds come from. Which fields are present depends on `type`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **destination** `TransferPartyData` Where the funds go. Same fields as `source`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **blockChain** `string` Chain the transaction runs on. **asset** `string` Asset moved. **amount** `string` Amount as a decimal string. **txHash** `string` On-chain transaction hash, once broadcast. **fees** `Fees` Fees charged. See [Fees](/api-basics/data-models#fees). **error** `string` Failure reason, if the transaction failed. **balanceChanges** `TransactionOperationBalanceChanges | null` Aggregate balance movements across the transaction, when present: the input asset debited and the output asset credited. See [TransactionOperationBalanceChanges](/api-basics/data-models#transactionoperationbalancechanges). **Properties** **changes** `TransactionOperationBalanceChange[]` One entry per party and asset. See [TransactionOperationBalanceChange](/api-basics/data-models#transactionoperationbalancechange). **Properties** **party** `TransferPartyData | null` Party whose balance changed. Same fields as `source`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **asset** `string` Asset symbol. **amount** `string` Signed decimal string: negative for a debit, positive for a credit. **chain** `string` Blockchain, for crypto movements. **paymentRail** `string` Payment rail, for fiat movements. **quoteResponse** `QuoteResponseItem` The executed quote, including its `input`, `output`, `source`, `destination`, and `expiresAt`. See [QuoteResponseItem](/api-basics/data-models#quoteresponseitem). **depositInstructions** `DepositInstructions` Present when funding is required. Use the returned details exactly. See [DepositInstructions](/api-basics/data-models#depositinstructions). ```json Request example theme={null} { "path": { "transactionId": "transaction_123" } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request GET \ --url "https://api.primevault.com/api/external/transactions/transaction_123/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Accept: application/json" ``` ```typescript JavaScript SDK theme={null} getTransactionById(transactionId: string): Promise ``` ```json Response example theme={null} { "id": "transaction_123", "orgId": "org_456", "vaultId": "vault_123", "status": "COMPLETED", "transactionType": "OUTGOING", "category": "TRANSFER", "subCategory": "EXTERNAL_TRANSFER", "createdAt": "2026-08-04T10:40:00Z", "updatedAt": "2026-08-04T10:42:15Z", "isDeleted": false, "externalId": "invoice-1001", "memo": "Vendor payout #1001", "source": { "type": "VAULT", "id": "vault_123", "subOrgId": "sub_org_123", "name": "Treasury Vault" }, "destination": { "type": "CONTACT", "id": "contact_456", "name": "Operations Wallet", "address": "0xAbC1234567890abcdef1234567890abcdef1234", "chain": "ETHEREUM" }, "blockChain": "ETHEREUM", "asset": "ETH", "amount": "0.50", "txHash": "0x9f4e6c1b7a2d3e4f", "balanceChanges": { "changes": [ { "party": { "type": "VAULT", "id": "vault_123", "subOrgId": "sub_org_123", "name": "Treasury Vault" }, "asset": "ETH", "amount": "-0.50", "chain": "ETHEREUM" }, { "party": { "type": "CONTACT", "id": "contact_456", "name": "Operations Wallet", "address": "0xAbC1234567890abcdef1234567890abcdef1234", "chain": "ETHEREUM" }, "asset": "ETH", "amount": "0.50", "chain": "ETHEREUM" } ] } } ``` ```typescript JavaScript SDK theme={null} interface Transaction { id: string; orgId: string; vaultId: string; status: TransactionStatus | string; transactionType: TransactionType | string; category: TransactionCategory | string; subCategory: TransactionSubCategory | string; createdAt: string; updatedAt: string; isDeleted: boolean; amount: string; txHash?: string; error?: string; externalId?: string; createdById?: string; fees?: Fees; memo?: string; txnSignature?: string; txnSignatureData?: Record; output?: TransactionOutput; amountInUSD?: string; nonce?: number; dAppId?: string; source?: TransferPartyData; destination?: TransferPartyData; quoteResponse?: QuoteResponseItem; depositInstructions?: DepositInstructions; operations?: TransactionOperation[]; balanceChanges?: TransactionOperationBalanceChanges | null; blockChain?: string; asset?: string; /** Deprecated: use destination.address. */ toAddress?: string; /** Deprecated: use destination.name. */ toAddressName?: string; /** Deprecated: use destination.id. */ toVaultId?: string; /** Deprecated: use source.address. */ sourceAddress?: string; } ``` # Trade Source: https://docs.primevault.com/transactions/trade Buy or sell USDT or USDC for USD, EUR, GBP or AED into your own account. Request a quote, execute it, then track the transaction to completion. Use Trade to convert between stablecoins and fiat in either direction. The same quote flow funds or empties your trading vault in fiat or crypto. See [Deposit into the LP vault](/payment-flows/trade/deposit-into-lp-vault) and [Withdraw from the LP vault](/payment-flows/trade/withdraw-from-lp-vault). > [!NOTE] > > This flow is for **first-party** conversions only: the source and destination, whether a wallet or a bank account, must both be yours. It is not available for paying third parties. ## API flow **1. Get a quote** Specify the input asset, the output asset, and exactly one amount. Source and destination are optional. Save the `quoteId` of the quote you choose. See [Get Quote](/transactions/trade/get-quote). **2. Execute the quote** Create a transaction from the `quoteId` before the quote expires. See [Execute Quote](/transactions/trade/execute-quote). **3. Approve the transaction** The new transaction is `PENDING` until approved. [Get the approval message](/approvals/get-approval-message), sign it, and [submit the approval](/approvals/submit-approval-action). The SDK's `createTransactionFromIntent()` does this for you. See [Approvals](/approvals/overview). **4. Track the transaction** Use [Retrieve Transaction](/transactions/retrieve-transaction) or [webhooks](/api-basics/webhooks) to track it until it reaches `COMPLETED`, `FAILED`, or `DECLINED`. > [card] [Get Quote](/transactions/trade/get-quote) > [card] [Execute Quote](/transactions/trade/execute-quote) # Execute a Trade Quote Source: https://docs.primevault.com/transactions/trade/execute-quote POST https://api.primevault.com/api/external/transactions/intent/create/ Create a trade transaction from a selected quoteId before the quote expires. Returns HTTP 201 with the full Transaction object. **POST** `/api/external/transactions/intent/create/` **Example:** executes the trade quote `quote_lp_best_3043`, selling 10,000 USDT for 9,997 USD. ## Request body **quoteId** `string` *required* The `quoteId` returned by [Get Quote](/transactions/trade/get-quote). **intent** `null` Send `null` when executing a quote. Do not repeat the quote intent. **externalId** `string` Client reference. **memo** `string` Human-readable note. ## Response Returns HTTP 201 with a full [Transaction](/api-basics/data-models#transaction). Optional fields depend on the payment route and status. **id** `string` Transaction ID. Use it with [Retrieve Transaction](/transactions/retrieve-transaction) to track status. **status** `string` Lifecycle status. Track it until `COMPLETED`, `FAILED`, or `DECLINED`. **quoteResponse** `QuoteResponseItem` The executed quote, including its `input`, `output`, `source`, `destination`, and `expiresAt`. See [QuoteResponseItem](/api-basics/data-models#quoteresponseitem). **source** `TransferPartyData` Where the funds come from, as a [TransferPartyData](/api-basics/data-models#transferpartydata). Which fields are present depends on `type`. **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **destination** `TransferPartyData` Where the funds go. Same fields as `source`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **Properties** **type** `string` `VAULT`, `BANK_ACCOUNT`, `CONTACT`, `EXTERNAL_ADDRESS`, or `EXTERNAL_BANK_ACCOUNT`. **id** `string` ID of the vault, bank account, or contact. **name** `string` Display name. **address** `string` Wallet address, for crypto parties. **chain** `string` Blockchain, for crypto parties. **paymentRail** `string` Payment rail, for bank parties, such as `ACH`. **balanceChanges** `TransactionOperationBalanceChanges | null` Aggregate balance movements across the transaction, when present: the input asset debited and the output asset credited. See [TransactionOperationBalanceChanges](/api-basics/data-models#transactionoperationbalancechanges). **Properties** **changes** `TransactionOperationBalanceChange[]` One entry per party and asset. See [TransactionOperationBalanceChange](/api-basics/data-models#transactionoperationbalancechange). **Properties** **party** `TransferPartyData | null` Party whose balance changed. Same fields as `source`. See [TransferPartyData](/api-basics/data-models#transferpartydata). **asset** `string` Asset symbol. **amount** `string` Signed decimal string: negative for a debit, positive for a credit. **chain** `string` Blockchain, for crypto movements. **paymentRail** `string` Payment rail, for fiat movements. **depositInstructions** `DepositInstructions` Present when funding is required. Use the returned details exactly. See [DepositInstructions](/api-basics/data-models#depositinstructions). > [!NOTE] > > Executing a quote creates a transaction in `PENDING` that waits for approval. [Get the approval message](/approvals/get-approval-message) for the transaction `id`, sign it, then [submit the approval](/approvals/submit-approval-action). The SDK's `createTransactionFromIntent()` does this for you and returns the updated transaction. See [Approvals](/approvals/overview). ```json Request example theme={null} { "intent": null, "quoteId": "quote_lp_best_3043", "externalId": "trade-usdt-usd-3043", "memo": "Sell 10000 USDT for USD" } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/transactions/intent/create/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "intent": null, "quoteId": "quote_lp_best_3043", "externalId": "trade-usdt-usd-3043", "memo": "Sell 10000 USDT for USD" }' ``` ```typescript JavaScript SDK theme={null} const transaction = await apiClient.createTransactionFromIntent({ quoteId: "quote_lp_best_3043", externalId: "trade-usdt-usd-3043", memo: "Sell 10000 USDT for USD", }); console.log(transaction.id, transaction.status); ``` ```json Response example theme={null} { "id": "transaction_trade_3043", "orgId": "org_456", "vaultId": "vault_gsr_lp", "status": "PENDING", "transactionType": "OUTGOING", "createdAt": "2026-09-30T10:00:05Z", "updatedAt": "2026-09-30T10:00:05Z", "isDeleted": false, "externalId": "trade-usdt-usd-3043", "memo": "Sell 10000 USDT for USD", "source": { "type": "VAULT", "id": "vault_gsr_lp" }, "destination": { "type": "VAULT", "id": "vault_gsr_lp" }, "balanceChanges": { "changes": [ { "party": { "type": "VAULT", "id": "vault_gsr_lp" }, "asset": "USDT", "amount": "-10000.00" }, { "party": { "type": "VAULT", "id": "vault_gsr_lp" }, "asset": "USD", "amount": "9997.00" } ] }, "quoteResponse": { "quoteId": "quote_lp_best_3043", "rate": "1.00030009", "fees": { "amount": "0", "asset": "USDT" }, "input": { "asset": "USDT", "amount": "10000.00" }, "output": { "asset": "USD", "amount": "9997.00" }, "source": { "type": "VAULT", "id": "vault_gsr_lp" }, "destination": { "type": "VAULT", "id": "vault_gsr_lp" }, "expiresAt": "2026-09-30T10:00:30+00:00" }, "depositInstructions": { "type": "EXTERNAL_ADDRESS", "asset": "USDT", "chain": "ETHEREUM", "address": "" } } ``` ```typescript JavaScript SDK theme={null} interface Transaction { id: string; orgId: string; vaultId: string; status: TransactionStatus | string; transactionType: TransactionType | string; category: TransactionCategory | string; subCategory: TransactionSubCategory | string; createdAt: string; updatedAt: string; isDeleted: boolean; amount: string; txHash?: string; error?: string; externalId?: string; createdById?: string; fees?: Fees; memo?: string; txnSignature?: string; txnSignatureData?: Record; output?: TransactionOutput; amountInUSD?: string; nonce?: number; dAppId?: string; source?: TransferPartyData; destination?: TransferPartyData; quoteResponse?: QuoteResponseItem; depositInstructions?: DepositInstructions; operations?: TransactionOperation[]; balanceChanges?: TransactionOperationBalanceChanges | null; blockChain?: string; asset?: string; /** Deprecated: use destination.address. */ toAddress?: string; /** Deprecated: use destination.name. */ toAddressName?: string; /** Deprecated: use destination.id. */ toVaultId?: string; /** Deprecated: use source.address. */ sourceAddress?: string; } ``` # Get a Trade Quote Source: https://docs.primevault.com/transactions/trade/get-quote POST https://api.primevault.com/api/external/transactions/v2/quote/ Request priced quotes to buy or sell a stablecoin against fiat by giving the input asset, output asset, and exactly one amount. Source and destination are optional. **POST** `/api/external/transactions/v2/quote/` **Example:** a quote to sell 10,000 USDT for USD without specifying a source or destination, so the returned quote supplies both. To buy, swap the input and output assets. ## Request body **intent** `TransactionIntentRequest` *required* Quote criteria for the requested trade. See [TransactionIntentRequest](/api-basics/data-models#transactionintentrequest). **Properties** **intent.input** `IntentAsset` *required* What you spend. See [IntentAsset](/api-basics/data-models#intentasset). **Properties** **intent.input.asset** `string` *required* Asset symbol, such as `USDT` when selling or `USD` when buying. **intent.input.amount** `string` Decimal amount to spend. Provide either this or `intent.output.amount`, not both. **intent.input.vaultId** `string` Vault or account that holds the input asset. **intent.output** `IntentAsset` *required* What you receive. See [IntentAsset](/api-basics/data-models#intentasset). **Properties** **intent.output.asset** `string` *required* Asset symbol, such as `USD` when selling or `USDC` when buying. **intent.output.amount** `string` Decimal amount to receive. Provide either this or `intent.input.amount`, not both. **intent.output.vaultId** `string` Vault or account that receives the output asset. **intent.source** `TransferPartyData` Optional source endpoint. When omitted, the quote returns the source to use. See [TransferPartyData](/api-basics/data-models#transferpartydata). **intent.destination** `TransferPartyData` Optional destination endpoint. When omitted, the quote returns the destination to use. See [TransferPartyData](/api-basics/data-models#transferpartydata). > [!NOTE] > > Supply both assets and exactly one non-null amount, as a decimal string. Omit the other amount. ## Response **quotes** `QuoteResponseItem[]` Available quotes for the intent. See [QuoteResponseItem](/api-basics/data-models#quoteresponseitem). **Properties** **quoteId** `string` Quote identifier. Pass it to [Execute Quote](/transactions/trade/execute-quote). **rate** `string | null` Quoted conversion rate. **fees** `Fees | null` Fee `amount`, `asset`, and optional `amountInFiat`. See [Fees](/api-basics/data-models#fees). **input** `IntentAsset` Quoted input asset and amount. See [IntentAsset](/api-basics/data-models#intentasset). **output** `IntentAsset` Quoted output asset and amount. See [IntentAsset](/api-basics/data-models#intentasset). **source** `TransferPartyData` Source endpoint for the trade. See [TransferPartyData](/api-basics/data-models#transferpartydata). **destination** `TransferPartyData` Destination endpoint for the trade. See [TransferPartyData](/api-basics/data-models#transferpartydata). **expiresAt** `string` ISO 8601 time when the quote expires. Execute the quote before this time. ```json Request example theme={null} { "intent": { "input": { "asset": "USDT", "amount": "10000.00" }, "output": { "asset": "USD" } } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/transactions/v2/quote/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "intent": { "input": { "asset": "USDT", "amount": "10000.00" }, "output": { "asset": "USD" } } }' ``` ```typescript JavaScript SDK theme={null} const quoteResponse = await apiClient.getQuote({ intent: { input: { asset: "USDT", amount: "10000.00" }, output: { asset: "USD" }, }, }); console.log(quoteResponse.quotes); ``` ```json Response example theme={null} { "quotes": [ { "quoteId": "quote_lp_best_3043", "rate": "1.00030009", "fees": { "amount": "0", "asset": "USDT" }, "input": { "asset": "USDT", "amount": "10000.00" }, "output": { "asset": "USD", "amount": "9997.00" }, "source": { "type": "VAULT", "id": "vault_gsr_lp" }, "destination": { "type": "VAULT", "id": "vault_gsr_lp" }, "expiresAt": "2026-09-30T10:00:30+00:00" } ] } ``` ```typescript JavaScript SDK theme={null} interface QuoteResponse { quotes: QuoteResponseItem[]; } interface QuoteResponseItem { quoteId: string; rate?: string | null; fees?: Fees | null; input?: IntentAsset; output?: IntentAsset; source?: TransferPartyData; destination?: TransferPartyData; expiresAt?: string; } ``` # Create a Vault Source: https://docs.primevault.com/vaults/create-vault POST https://api.primevault.com/api/external/vaults/ Create a new vault in your organization. The vault needs your API user's approval before it takes effect. **POST** `/api/external/vaults/` ## Request body **vaultName** `string` *required* Vault name. Cannot be blank. Naming it "NGN" does not set its asset; asset is not a create input. **subOrgId** `string` Sub-org that owns the vault. **chains** `string[]` Requested wallet chains. Use identifiers from [Assets and Chains](/assets-and-chains/overview). **testNetVault** `boolean` Creates a testnet vault when supported. Default: false. **vaultGroupIds** `string[]` Organization-scoped vault groups to attach. ## Response Returns the new [Vault](/api-basics/data-models#vault). Wallet addresses can be generated after the response, so retrieve the vault until `walletsGenerated` is `true` before using them. **id** `string` Vault ID. **orgId** `string` Organization that owns the vault. **subOrgId** `string` [Sub-org](/sub-orgs/overview) that owns the vault, if any. **vaultName** `string` Vault name. **vaultType** `VaultType` `DEFAULT`, `EXCHANGE`, or `GAS`. See [VaultType](/api-basics/data-models#vaulttype). **asset** `string | null` Currency of a multi-currency account vault, for example `NGN`. `null` for self-custody vaults. **wallets** `object[]` Blockchain wallets in the vault. **Properties** **id** `string` Wallet ID. **blockchain** `string` Chain the wallet is on. **address** `string` Wallet address. Missing until the wallet is generated. **publicKey** `string` Wallet public key. **walletsGenerated** `boolean` Whether wallet addresses are ready. Wait for `true` before using `wallets`. **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the vault is deleted. ## Approve the vault Creating a vault opens a change request that your API user must approve. Call [Get Approval Message](/approvals/get-approval-message) with `entityId` set to the vault `id`, sign the `message`, and send the signature to [Submit Approval Action](/approvals/submit-approval-action). See [Approvals](/approvals/overview) for the full flow. With the SDK, approve separately or in one call: ```typescript theme={null} const vault = await apiClient.createVault(request); const approvedVault = await apiClient.createVaultApproval(vault); // Convenience helper: create, then approve. const approved = await apiClient.createVaultWithApproval(request); ``` After approval, retrieve the vault until `walletsGenerated === true` before using its wallet addresses. Approval does not make wallet generation synchronous. ```json Request example theme={null} { "vaultName": "Treasury Vault", "subOrgId": "sub_org_123", "chains": ["ETHEREUM", "POLYGON"], "testNetVault": false, "vaultGroupIds": ["vault_group_123"] } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request POST \ --url "https://api.primevault.com/api/external/vaults/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Content-Type: application/json" \ --data '{ "vaultName": "Treasury Vault", "subOrgId": "sub_org_123", "chains": [ "ETHEREUM", "POLYGON" ], "testNetVault": false, "vaultGroupIds": [ "vault_group_123" ] }' ``` ```typescript JavaScript SDK theme={null} interface CreateVaultRequest { vaultName: string; subOrgId?: string; chains?: string[]; testNetVault?: boolean; vaultGroupIds?: string[]; } createVault(request: CreateVaultRequest): Promise ``` ```json Response example theme={null} { "id": "vault_123", "orgId": "org_456", "subOrgId": "sub_org_123", "vaultName": "Treasury Vault", "vaultType": "DEFAULT", "wallets": [], "walletsGenerated": false, "createdAt": "2026-08-04T10:30:00Z", "updatedAt": "2026-08-04T10:30:00Z", "isDeleted": false, "asset": null } ``` ```typescript JavaScript SDK theme={null} interface Vault { id: string; orgId: string; subOrgId?: string; vaultName: string; vaultType: VaultType; asset?: string | null; wallets: Array<{ id: string; blockchain: string; address?: string; publicKey?: string; }>; walletsGenerated: boolean; createdAt: string; updatedAt: string; isDeleted: boolean; } ``` # List Vaults Source: https://docs.primevault.com/vaults/list-vaults GET https://api.primevault.com/api/external/vaults/ Retrieve a paginated list of vaults in your organization, optionally filtered by vault name. **GET** `/api/external/vaults/?limit={limit}&cursor={cursor}&{filters}` ## Query parameters **limit** `number` Requested page size. SDK default: 20. **cursor** `string | null` First page: null/empty. Subsequent pages: the preceding `nextCursor`. **Filters** `Record\` Resource filters, for example `vaultName`. ## Response Returns a page of [Vault](/api-basics/data-models#vault) objects. See [Cursor response](/api-basics/filtering-and-pagination#cursor-response). **results** `Vault[]` Vaults on this page. Each item is a [Vault](/api-basics/data-models#vault). **Properties** **id** `string` Vault ID. **orgId** `string` Organization that owns the vault. **subOrgId** `string` [Sub-org](/sub-orgs/overview) that owns the vault, if any. **vaultName** `string` Vault name. **vaultType** `VaultType` `DEFAULT`, `EXCHANGE`, or `GAS`. See [VaultType](/api-basics/data-models#vaulttype). **asset** `string | null` Currency of a multi-currency account vault, for example `NGN`. `null` for self-custody vaults. **wallets** `object[]` Blockchain wallets in the vault. **Properties** **id** `string` Wallet ID. **blockchain** `string` Chain the wallet is on. **address** `string` Wallet address. Missing until the wallet is generated. **publicKey** `string` Wallet public key. **walletsGenerated** `boolean` Whether wallet addresses are ready. Wait for `true` before using `wallets`. **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the vault is deleted. **nextCursor** `string | null` Cursor for the next page. `null` on the last page. **hasNext** `boolean` Whether another page exists. ```json Request example theme={null} { "query": { "limit": 20, "cursor": null, "vaultName": "Treasury" } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request GET \ --url "https://api.primevault.com/api/external/vaults/?limit=20&vaultName=Treasury" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Accept: application/json" ``` ```typescript JavaScript SDK theme={null} getVaults( params?: Record, limit?: number, cursor?: string | null, ): Promise const firstPage = await apiClient.getVaults( { vaultName: "Treasury" }, 20, null, ); ``` ```json Response example theme={null} { "results": [ { "id": "vault_123", "orgId": "org_456", "subOrgId": "sub_org_123", "vaultName": "Treasury Vault", "vaultType": "DEFAULT", "wallets": [ { "id": "wallet_123", "blockchain": "ETHEREUM", "address": "0x1234567890abcdef1234567890abcdef12345678", "publicKey": "0x04abcdef" } ], "walletsGenerated": true, "createdAt": "2026-08-04T10:30:00Z", "updatedAt": "2026-08-04T10:35:00Z", "isDeleted": false, "asset": null }, { "id": "vault_ngn_business", "orgId": "org_456", "subOrgId": "sub_org_123", "vaultName": "NGN Business Account", "vaultType": "DEFAULT", "asset": "NGN", "wallets": [], "walletsGenerated": false, "createdAt": "2026-09-27T10:00:00Z", "updatedAt": "2026-09-27T10:00:00Z", "isDeleted": false } ], "nextCursor": "eyJpZCI6InZhdWx0XzEyMyJ9", "hasNext": true } ``` ```typescript JavaScript SDK theme={null} interface VaultListResponse { results: Vault[]; nextCursor?: string | null; hasNext?: boolean; } ``` # Vaults Source: https://docs.primevault.com/vaults/overview List, retrieve, and create vaults, and read their balances and deposit instructions. A vault is an account that holds assets. There are two types: * **Multi-currency accounts:** one vault per currency, identified by its `asset` field. For example, an NGN vault has `asset: "NGN"` and a EUR vault has `asset: "EUR"`. Each works like a virtual account: a dedicated balance in one currency that you fund through its own [deposit instructions](/vaults/vault-deposit-instructions) and pay out from. * **Self-custody vaults:** hold on-chain assets in blockchain wallets you control. Addresses are listed in `wallets`, and `asset` is `null`. For how each type is used in payment flows, see [Multi-currency accounts](/payment-flows/accounts/multi-currency-accounts) and [Self-custody accounts](/payment-flows/accounts/self-custody-accounts). **To fund a vault**, call the [deposit instructions API](/vaults/vault-deposit-instructions) (`GET /api/external/vaults/{vaultId}/deposit_instructions/`) to get the details for sending funds into it. Use the other APIs below to list, retrieve, and create vaults and to check balances. > [!NOTE] > > **Lifecycle:** A new vault needs your API user's approval. See [Approvals](/approvals/overview). For blockchain wallets, creation can return before wallet addresses are generated. Treat `walletsGenerated` as the readiness signal; do not infer readiness from HTTP 201 alone. > [card] [List vaults](/vaults/list-vaults) > > **GET** `/api/external/vaults/` > [card] [Retrieve a vault](/vaults/retrieve-vault) > > **GET** `/api/external/vaults/{vaultId}/` > [card] [Create a vault](/vaults/create-vault) > > **POST** `/api/external/vaults/` > [card] [Read balances](/vaults/vault-balances) > > **GET** `/api/external/vaults/{vaultId}/detailed_balances/` > [card] [Get deposit instructions](/vaults/vault-deposit-instructions) > > **GET** `/api/external/vaults/{vaultId}/deposit_instructions/` # Retrieve a Vault Source: https://docs.primevault.com/vaults/retrieve-vault GET https://api.primevault.com/api/external/vaults/{vaultId}/ Retrieve a single vault by ID, including its wallets, asset, and sub-org. **GET** `/api/external/vaults/{vaultId}/` ## Path parameter **vaultId** `string` *required* ID returned by a vault list or create response. ## Response Returns a [Vault](/api-basics/data-models#vault). **id** `string` Vault ID. **orgId** `string` Organization that owns the vault. **subOrgId** `string` [Sub-org](/sub-orgs/overview) that owns the vault, if any. **vaultName** `string` Vault name. **vaultType** `VaultType` `DEFAULT`, `EXCHANGE`, or `GAS`. See [VaultType](/api-basics/data-models#vaulttype). **asset** `string | null` Currency of a multi-currency account vault, for example `NGN`. `null` for self-custody vaults. **wallets** `object[]` Blockchain wallets in the vault. **Properties** **id** `string` Wallet ID. **blockchain** `string` Chain the wallet is on. **address** `string` Wallet address. Missing until the wallet is generated. **publicKey** `string` Wallet public key. **walletsGenerated** `boolean` Whether wallet addresses are ready. Wait for `true` before using `wallets`. **createdAt** `string` Creation timestamp (ISO 8601). **updatedAt** `string` Last update timestamp (ISO 8601). **isDeleted** `boolean` Whether the vault is deleted. ```json Request example theme={null} { "path": { "vaultId": "vault_ngn_business" } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request GET \ --url "https://api.primevault.com/api/external/vaults/vault_ngn_business/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Accept: application/json" ``` ```typescript JavaScript SDK theme={null} const vault = await apiClient.getVaultById("vault_ngn_business"); console.log(vault.id, vault.asset); // vault_ngn_business, NGN ``` ```json Response example theme={null} { "id": "vault_ngn_business", "orgId": "org_456", "subOrgId": "sub_org_123", "vaultName": "NGN Business Account", "vaultType": "DEFAULT", "asset": "NGN", "wallets": [], "walletsGenerated": false, "createdAt": "2026-09-27T10:00:00Z", "updatedAt": "2026-09-27T10:00:00Z", "isDeleted": false } ``` ```typescript JavaScript SDK theme={null} interface Vault { id: string; orgId: string; subOrgId?: string; vaultName: string; vaultType: VaultType; asset?: string | null; wallets: Array<{ id: string; blockchain: string; address?: string; publicKey?: string; }>; walletsGenerated: boolean; createdAt: string; updatedAt: string; isDeleted: boolean; } ``` # Get Vault Balances Source: https://docs.primevault.com/vaults/vault-balances GET https://api.primevault.com/api/external/vaults/{vaultId}/detailed_balances/ Read a vault's balance per asset, including symbol, chain, token address, and optional USD value and price. **GET** `/api/external/vaults/{vaultId}/detailed_balances/` Balances show what the vault holds, one entry per asset. To get the details for funding a vault, see [Deposit Instructions](/vaults/vault-deposit-instructions). ## Path parameters **vaultId** `string` *required* ID of the vault to read balances for. ## Response Returns a [DetailedBalanceResponse](/api-basics/data-models#detailedbalanceresponse): an array of [DetailedBalance](/api-basics/data-models#detailedbalance) objects, one entry per balance. **symbol** `string` Asset symbol. **balance** `string` Balance as a decimal string. **name** `string` Asset name. **chain** `string` Chain the balance is on. **tokenAddress** `string` Token contract address, for on-chain tokens. **balanceInUSD** `string` Balance value in USD, when available. **price** `string` Asset price used for `balanceInUSD`, when available. ```json Request example theme={null} { "path": { "vaultId": "vault_123" }, "query": {} } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request GET \ --url "https://api.primevault.com/api/external/vaults/vault_123/detailed_balances/" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Accept: application/json" ``` ```typescript JavaScript SDK theme={null} getDetailedBalances( vaultId: string, params?: Record, ): Promise ``` ```json Response example theme={null} [ { "symbol": "USDC", "balance": "125.50", "name": "USD Coin", "chain": "ETHEREUM", "tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" }, { "symbol": "USD", "balance": "2500.00" } ] ``` ```typescript JavaScript SDK theme={null} interface DetailedBalance { symbol: string; balance: string; name?: string; chain?: string; tokenAddress?: string; balanceInUSD?: string; price?: string; } type DetailedBalanceResponse = DetailedBalance[]; ``` # Get Vault Deposit Instructions Source: https://docs.primevault.com/vaults/vault-deposit-instructions GET https://api.primevault.com/api/external/vaults/{vaultId}/deposit_instructions/ Get the bank or crypto details used to fund a vault. Send an asset plus exactly one payment rail or chain, and use the returned details exactly. **GET** `/api/external/vaults/{vaultId}/deposit_instructions/` Deposit instructions show how to fund a vault, like the bank details you share to receive a transfer. To read what a vault already holds, see [Balances](/vaults/vault-balances). ## Path parameters **vaultId** `string` *required* ID of the vault to fund. ## Query parameters Send `asset` and exactly one of `paymentRail` (bank) or `chain` (crypto). Choose a rail or chain supported by the account. **asset** `string` *required* Asset to deposit, for example `NGN` or `USDC`. **paymentRail** `string` Bank payment rail, for example `NIP`. Do not send with `chain`. **chain** `string` Blockchain for a crypto deposit, for example `ETHEREUM`. Do not send with `paymentRail`. ## Response Returns a [VaultDepositInstructionsResponse](/api-basics/data-models#vaultdepositinstructionsresponse). `results` may be empty or contain several options. Use the returned details exactly. **results** `DepositInstructions[]` Ways to fund the vault. Each item is a [DepositInstructions](/api-basics/data-models#depositinstructions). **Properties** **type** `TransferPartyType | string` Kind of destination to send funds to. See [TransferPartyType](/api-basics/data-models#transferpartytype). **paymentRail** `string` Payment rail, for fiat deposits. **bankDetails** `BankDetails` Bank details to send fiat to. See [BankDetails](/api-basics/data-models#bankdetails). **Properties** **accountName** `string` Name of the receiving bank account. **bankAccountInfo** `BankAccountInfo` Bank and account identifiers of the receiving account. See [BankAccountInfo](/api-basics/data-models#bankaccountinfo). **Properties** **country** `string | null` Country code of the bank. **accountNumber** `string | null` Bank account number. **routingNumber** `string | null` Domestic routing identifier where applicable. **bankName** `string | null` Bank name. **bankAddress** `string | null` Bank address. **pixKey** `string | null` PIX key. Used with the `PIX` rail. **pixKeyType** `string | null` Type of the PIX key. **iban** `string | null` International Bank Account Number. **bic** `string | null` Bank Identifier Code (SWIFT code). **accountType** `string | null` `CHECKING` or `SAVINGS`. **beneficiary** `DepositInstructionBeneficiary` Holder of the receiving bank account. See [DepositInstructionBeneficiary](/api-basics/data-models#depositinstructionbeneficiary). **Properties** **name** `string` Beneficiary name. **address** `Partial\` Beneficiary address. Every field is optional. **Properties** **streetLine1** `string` First street address line. **streetLine2** `string | null` Second street address line. **city** `string` City. **state** `string | null` State or province. **zip** `string` Postal code. **country** `string` Country code. **asset** `string` Asset to deposit. **address** `string` Address to send crypto to. **chain** `string` Chain of the deposit address. **memo** `string` Memo or reference to include with the deposit, if required. ```json Request example theme={null} { "path": { "vaultId": "vault_123" }, "query": { "asset": "USDC", "chain": "ETHEREUM" } } ``` ```bash cURL theme={null} # Sample only. Generate a new signed token for each request # (see Authentication). Tokens expire after 120 seconds. curl --request GET \ --url "https://api.primevault.com/api/external/vaults/vault_123/deposit_instructions/?asset=USDC&chain=ETHEREUM" \ --header "Authorization: Bearer " \ --header "Api-Key: " \ --header "Accept: application/json" ``` ```typescript JavaScript SDK theme={null} // Bank funding: asset plus paymentRail. const bankFunding = await apiClient.getVaultDepositInstructions( "vault_ngn_business", { asset: "NGN", paymentRail: "NIP" }, ); // results can contain zero, one, or multiple instructions. for (const instruction of bankFunding.results) { console.log(instruction.asset, instruction.paymentRail); // Present instruction.bankDetails and instruction.memo in your funding UI. } // Crypto funding: asset plus chain. const cryptoFunding = await apiClient.getVaultDepositInstructions( "vault_123", { asset: "USDC", chain: "ETHEREUM" }, ); console.log(cryptoFunding.results); ``` ```json Response example theme={null} { "results": [ { "type": "VAULT", "asset": "USDC", "chain": "ETHEREUM", "address": "0xAbC1234567890abcdef1234567890abcdef1234" } ] } ``` ```typescript JavaScript SDK theme={null} interface VaultDepositInstructionsResponse { results: DepositInstructions[]; } interface DepositInstructions { type?: TransferPartyType | string; paymentRail?: string; bankDetails?: BankDetails; asset?: string; address?: string; chain?: string; memo?: string; } interface BankDetails { accountName?: string; bankAccountInfo?: BankAccountInfo; beneficiary?: DepositInstructionBeneficiary; } interface DepositInstructionBeneficiary { name?: string; address?: Partial; } ```