Overview
Circle Payments Network (CPN) is an account-based off-ramp provider that converts USDC to fiat through smart contract settlement. This guide covers the full API integration: discovering requirements, quote, order creation, smart contract settlement signing, tracking, and the Requirements (RFI) flow.
The examples use USDC → BRL via PIX as a concrete reference. The CPN integration supports all assets and rails that CPN itself supports: USDC across the blockchains CPN settles on, and CPN’s full set of fiat currencies and payment rails. For another corridor, substitute the appropriate baseAssetId (USDC on your chain), quoteAssetId, quoteAssetRail, and whitelisted destination; the flow and request structures are identical.
Requirements
Fireblocks workspace
- A Fireblocks workspace with a connected and approved CPN account.
- A vault account holding USDC on the blockchain you want to settle from (for example, Ethereum, Polygon, or Solana).
- The native blockchain asset in the vault to cover the gas fee on the settlement transaction (e.g. ETH, MATIC, or SOL).
- A whitelisted external wallet for the fiat destination. The wallet must have status:
"APPROVED" before it can be used in an order.
API user
Create a Fireblocks API user in the Console, download the .pem private key, and note the API key UUID. Every request must be signed as a JWT using RS256. See Authenticate: Signing a Request for the full JWT structure.
Install the SDK:
npm install @fireblocks/ts-sdk
Initialize:
import { Fireblocks, BasePath } from "@fireblocks/ts-sdk";
import fs from "fs";
const fireblocks = new Fireblocks({
apiKey: "<your-api-key-uuid>",
secretKey: fs.readFileSync("./fireblocks_secret.pem", "utf8"),
basePath: BasePath.US,
});
SDK namespace for all trading operations: fireblocks.tradingBeta.*
Required policy rules
Configure the following policy rules in the Console before placing any orders. Set Designated Signers to specific named users; using Initiator on API-key-initiated orders will cause signing requests to stall unless a co-signer is configured.
| Rule | Purpose |
|---|
| Order Policy | Approves the DVP settlement transaction that moves USDC from the source vault |
| Approve (EVM only) | Grants Permit2 permission to move USDC. One-time per wallet + asset combination |
| Typed Message (EVM only) | Signs the transfer authorization per order |
| Program Call (Solana only) | Authorizes the Solana program call. No separate Approval needed |
CPN settles through a smart contract, so it does not require a Transfer Policy rule; the Approve, Typed Message, and Program Call rules authorize the asset movement instead.
Flow
The integration follows this order: Discover requirements → Quote → Order → Sign → Track. If the order enters AWAITING_INFORMATION at any point, there is an additional stage for the Requirements (RFI) flow.
Step 1: Discover CPN’s PII requirements
CPN requires participant identification (PII), and the required fields depend on the destination rail; each rail (PIX, SEPA, WIRE, SPEI, etc.) has its own PII requirements. There are two ways to discover them; select one, based on your integration preference:
Up front, via the providers endpoint
GET /v1/trading/providers
Returns the available providers and, for CPN, its capabilities and participant-identification requirements per rail. Use this to build your data model before requesting a quote. See the Get Providers reference.
At quote time, via the quote response
The quote response (Step 2) includes requiredParticipantsIdentificationOnOrder, a JSON Schema string describing the exact PII fields required for that specific corridor and amount. Parse it to build your order payload.
Both options describe the same requirement. The first lets you prepare a PII collection before quoting; the second gives you the precise, corridor- and amount-specific schema at quote time.
Step 2: Get a quote
A quote returns a time-limited price for the conversion. CPN requires minimal PII at quote time; just entity types and country codes for originator and beneficiary.
POST /v1/trading/quotes
Request Body
{
"scope": [
{
"providerId": "CPN",
"accountId": "<your-cpn-account-id>"
}
],
"baseAssetId": "USDC_SEP",
"quoteAssetId": "BRL",
"quoteAssetRail": "PIX",
"baseAmount": "100.00",
"side": "SELL",
"participantsIdentification": {
"originator": {
"entityType": "INDIVIDUAL",
"postalAddress": { "country": "US" }
},
"beneficiary": {
"entityType": "INDIVIDUAL",
"postalAddress": { "country": "BR" }
}
}
}
Field notes:
scope: ties the quote to a specific CPN account in your workspace.
baseAssetId / quoteAssetId: the sell and receive assets. For Solana use SOL_USDC_JKVK.
quoteAssetRail: "PIX": required for BRL; tells CPN to route via the PIX rail.
participantsIdentification: CPN requires this at quote time. Minimum accepted values are entity type + country.
Expected Response (201)
{
"quotes": [
{
"id": "q_abc123...",
"type": "COMMITTED",
"via": { "providerId": "CPN", "accountId": "<your-cpn-account-id>" },
"baseAssetId": "USDC_SEP",
"quoteAssetId": "BRL",
"baseAmount": "100.00",
"quoteAmount": "520.00",
"expiresAt": "2025-01-01T12:05:00.000Z",
"requiredParticipantsIdentificationOnOrder": "{\"type\":\"object\",\"required\":[\"originator\",\"beneficiary\"],\"properties\":{...}}"
}
],
"quoteFailures": []
}
Key field: requiredParticipantsIdentificationOnOrder: a JSON Schema Draft-7 string describing the exact PII fields CPN requires on the order request. Parse it before building your order payload; the schema can vary by provider, corridor, and amount. Do not hardcode a PII structure; read it from the quote.
What to verify:
quotes[0].type is "COMMITTED" (a firm, executable price).
quoteAmount is a non-zero BRL value.
expiresAt gives you a time window to place the order (typically 30–60 seconds).
requiredParticipantsIdentificationOnOrder is present and parseable as JSON Schema.
Step 3: Create an order
Use the quoteId from the previous step to create an order. The order must be placed before the quote’s expiresAt timestamp. This step requires the full KYC PII payload validated against the JSON Schema returned in requiredParticipantsIdentificationOnOrder.
POST /v1/trading/orders
Request Body
{
"via": {
"type": "PROVIDER_ACCOUNT",
"providerId": "CPN",
"accountId": "<your-cpn-account-id>"
},
"executionRequestDetails": {
"type": "QUOTE",
"quoteId": "<quote-id-from-step-2>"
},
"settlement": {
"type": "DVP",
"sourceAccount": {
"type": "VAULT_ACCOUNT",
"accountId": "<your-source-vault-id>"
},
"destinationAccount": {
"type": "UNMANAGED_WALLET",
"accountId": "<your-whitelisted-brl-wallet-id>"
}
},
"participantsIdentification": {
"originator": {
"entityType": "INDIVIDUAL",
"fullName": {
"firstName": "Jane",
"lastName": "Doe"
},
"postalAddress": {
"streetName": "Main Street",
"buildingNumber": "123",
"postalCode": "10001",
"city": "New York",
"country": "US"
},
"nationality": "US",
"phone": "+15550123456",
"dateOfBirth": "1990-01-01",
"externalReferenceId": "internal-user-id-001"
},
"beneficiary": {
"entityType": "INDIVIDUAL",
"fullName": {
"firstName": "John",
"lastName": "Silva"
},
"postalAddress": {
"streetName": "Rua das Flores",
"buildingNumber": "1",
"postalCode": "28990-001",
"city": "Rio de Janeiro",
"country": "BR"
},
"nationality": "BR",
"dateOfBirth": "1990-01-01",
"externalReferenceId": "internal-beneficiary-id-001",
"identificationDocuments": [
{
"id": "390.533.447-05",
"type": "NATIONAL_ID",
"expirationDate": "2030-12-31"
}
]
}
},
"sourceOfFunds": {
"reasonForPayment": "PROPERTY_RENTAL"
},
"customerInternalReferenceId": "test-order-ref-001"
}
Field notes:
-
via: must match the provider and account used in the quote’s scope.
-
executionRequestDetails.type: "QUOTE": ties the order to a pre-fetched quote. The amount is already baked into the quote; do not specify it again.
-
settlement.sourceAccount: the vault holding the USDC to sell.
-
settlement.destinationAccount: the whitelisted external wallet that receives the BRL payout.
-
participantsIdentification: full KYC. The required fields are defined by the requiredParticipantsIdentificationOnOrder schema in the quote response. The example above covers the fields CPN requires for this corridor.
-
sourceOfFunds.reasonForPayment: required by CPN for cross-border compliance. Supported values:
INVOICE_PAYMENT
SERVICES_PAYMENT
SOFTWARE_PAYMENT
IMPORTED_GOODS_PAYMENT
TRAVEL_SERVICES
TRANSFER_TO_OWN_ACCOUNT
LOAN_REPAYMENT
PAYROLL
PROPERTY_RENTAL
INFORMATION_SERVICE_CHARGES
ADVERTISING_AND_PUBLIC_RELATIONS
INTELLECTUAL_PROPERTY_FEES
FINANCIAL_SERVICE_FEES
ADVISORY_AND_TECHNICAL_FEES
REPRESENTATIVE_OFFICE_EXPENSES
TAX_PAYMENT
GOODS_TRANSPORTATION_FEES
CONSTRUCTION_EXPENSES
INSURANCE_PREMIUM
GENERAL_GOODS_TRADE
INSURANCE_CLAIMS_PAYMENT
FAMILY_REMITTANCE
EDUCATION_EXPENSES
MEDICAL_TREATMENT
DONATIONS
MUTUAL_FUND_INVESTMENT
CURRENCY_EXCHANGE
ADVANCE_GOODS_PAYMENT
MERCHANT_SETTLEMENT
REPATRIATION_FUND_SETTLEMENT
-
customerInternalReferenceId: optional, but useful for correlating orders with your internal records.
Expected Response (202)
{
"id": "ord_xyz789...",
"status": "CREATED",
"via": { "providerId": "CPN", "accountId": "<your-cpn-account-id>" },
"createdAt": "2025-01-01T12:00:00.000Z",
"updatedAt": "2025-01-01T12:00:00.000Z",
"executionSteps": []
}
What to verify:
- Response status is
202 Accepted.
order.id (UUID) is returned; save this for the tracking step.
order.status is "CREATED".
Step 4: Smart contract settlement signing
Once the order is created, the designated signer receives signing requests in the Fireblocks mobile app. The number and type of requests depend on the source network:
Ethereum / Polygon (EVM):
- Permit2 approval (first use only, per wallet + asset combination): a typed message signing request that grants CPN permission to move USDC from the source wallet via the Permit2 contract. This is a one-time operation; subsequent orders on the same wallet and asset will skip this step.
- Typed message signing: the actual transfer authorization. The signer approves a structured EIP-712 message in the mobile app for each order.
Solana:
- Program call signing: the signer approves a Solana program instruction in the mobile app. There is no separate approval step; a single program call covers the full transfer.
The order remains in PROCESSING status while signing requests are pending. If the designated signer does not approve within the policy timeout window, the order transitions to FAILED. Ensure the signer has the Fireblocks mobile app installed and push notifications enabled.
Step 5: Track the order
Poll the order status until it reaches a terminal state (COMPLETED, FAILED, or CANCELED), or until it enters AWAITING_INFORMATION which requires the RFI flow (see Step 6 below).
GET /v1/trading/orders/{orderId}
Example
GET /v1/trading/orders/ord_xyz789...
Expected Response (200):
{
"id": "ord_xyz789...",
"status": "PROCESSING",
"via": { "providerId": "CPN", "accountId": "<your-cpn-account-id>" },
"createdAt": "2025-01-01T12:00:00.000Z",
"updatedAt": "2025-01-01T12:00:30.000Z",
"executionSteps": [
{
"type": "BLOCKCHAIN_TRANSFER",
"status": "PROCESSING",
"txId": "tx_abc..."
}
]
}
Order status lifecycle for a normal (happy path) flow:
CREATED → PROCESSING → COMPLETED
Order status lifecycle when an RFI is triggered:
CREATED → PROCESSING → AWAITING_INFORMATION → PROCESSING → COMPLETED
↑
(submit RFI response)
Polling recommendations:
- Poll every 5 seconds
- Stop automatically when status is
COMPLETED, FAILED, or CANCELED
- Pause polling and enter the RFI flow when status is
AWAITING_INFORMATION
- On a
FAILED order, check failure.reason and failure.failureInfo in the response for the provider error detail
CPN may require additional information after an order is created, e.g. enhanced KYC documentation or clarification on source of funds. When this happens, the order status transitions to AWAITING_INFORMATION. You must submit the required information before the order can proceed.
The RFI flow has three sub-steps:
- Fetch the requirement to find out what is needed.
- Upload any required files (one call per file).
- Submit the structured data payload.
6.1 — Fetch the order requirement
GET /v1/trading/orders/{orderId}/requirement
Example
GET /v1/trading/orders/ord_xyz789.../requirement
Expected Response (200)
{
"requirementId": "req_abc123...",
"dueBy": "2025-01-02T12:00:00.000Z",
"requiredData": "{\"type\":\"object\",\"required\":[\"sourceOfFunds\",\"verificationMethod\"],\"properties\":{\"sourceOfFunds\":{\"type\":\"string\",\"enum\":[\"SALARY\",\"SAVINGS\",\"BUSINESS_INCOME\"]},\"verificationMethod\":{\"type\":\"string\",\"enum\":[\"GOVERNMENT_ID\",\"BANK_STATEMENT\"]}}}",
"requiredFiles": [
{
"fileKey": "identity_document",
"description": "A government-issued photo ID (passport or national ID card).",
"allowedFileTypes": ["PDF", "PNG", "JPEG"]
}
]
}
Field notes:
requirementId: unique identifier for this RFI instance.
dueBy: deadline to submit. If overdue, the order is likely to FAIL or be CANCELED shortly after.
requiredData: a JSON Schema Draft-7 string. Parse it to determine which fields and values to send in the data submission (step 6.3).
requiredFiles: list of files to upload before submitting data. Each entry specifies a fileKey, a human-readable description, and allowed MIME types.
Errors
409 Conflict: the order exists but is not in AWAITING_INFORMATION. Check the current order status before calling this endpoint
404 Not Found: the order ID is invalid
6.2 — Upload required files
For each entry in requiredFiles, upload the file using a multipart/form-data request. Call this endpoint once per file.
POST /v1/trading/orders/{orderId}/requirement/file
Content-Type: multipart/form-data
Form fields:
| Field | Value |
|---|
fileKey | Matches the fileKey from the requirement (e.g., identity_document) |
file | The binary file content |
Example (curl)
BOUNDARY="----FormBoundary$(date +%s)"
curl -X POST "https://<base-url>/v1/trading/orders/ord_xyz789.../requirement/file" \
-H "Authorization: Bearer <jwt-token>" \
-H "X-API-Key: <api-key>" \
-H "Content-Type: multipart/form-data; boundary=${BOUNDARY}" \
--data-binary $"--${BOUNDARY}\r\nContent-Disposition: form-data; name=\"fileKey\"\r\n\r\nidentity_document\r\n--${BOUNDARY}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"passport.pdf\"\r\nContent-Type: application/pdf\r\n\r\n$(cat /path/to/passport.pdf)\r\n--${BOUNDARY}--"
Alternatively, using -F (curl handles the boundary automatically):
curl -X POST "https://<base-url>/v1/trading/orders/ord_xyz789.../requirement/file" \
-H "Authorization: Bearer <jwt-token>" \
-H "X-API-Key: <api-key>" \
-F "fileKey=identity_document" \
-F "file=@/path/to/passport.pdf;type=application/pdf"
Expected Response: 202 Accepted (empty body)
Fireblocks encrypts the file and forwards it to CPN. The file is not stored in plaintext. Upload all required files before submitting the data payload.
6.3 — Submit the data payload
After uploading all files, submit the structured data that satisfies the requiredData JSON Schema.
POST /v1/trading/orders/{orderId}/requirement/data
Request Body
{
"data": {
"sourceOfFunds": "SALARY",
"verificationMethod": "GOVERNMENT_ID"
}
}
The exact fields depend on what requiredData schema was returned in step 6.1. Parse the schema dynamically; do not hardcode field names.
Expected Response: 202 Accepted (empty body)
What happens next: CPN processes the submission asynchronously. After a successful submission, resume polling GET /v1/trading/orders/{orderId} until the order leaves AWAITING_INFORMATION. Expect a grace period of ~15 seconds before CPN updates the status.
An order can enter AWAITING_INFORMATION more than once if CPN requires multiple rounds of verification. Each time, repeat steps 6.1–6.3.
Order history
List all orders for your workspace with optional filters.
GET /v1/trading/orders
Supported query parameters: providerId, status, createdAfter, createdBefore, limit, next (cursor for pagination).
API reference summary
All endpoints are prefixed with /v1. Production base URLs:
| Region | Base URL |
|---|
| US | https://api.fireblocks.io/v1 |
| EU | https://eu-api.fireblocks.io/v1 |
| EU2 | https://eu2-api.fireblocks.io/v1 |
| Method | Path | Purpose |
|---|
GET | /trading/providers | List providers and connected accounts; discover CPN’s per-rail PII requirements |
POST | /trading/quotes | Create a quote |
POST | /trading/orders | Create an order from a quote |
GET | /trading/orders/{orderId} | Get current order status and details |
GET | /trading/orders | List orders (pagination + filters) |
GET | /trading/orders/{orderId}/requirement | Fetch RFI requirement details |
POST | /trading/orders/{orderId}/requirement/file | Upload a file for an RFI |
POST | /trading/orders/{orderId}/requirement/data | Submit structured data for an RFI |
Authentication: Every request requires:
Authorization: Bearer <jwt>: RS256 JWT signed with your API private key.
X-API-Key: <api-key-uuid>: your workspace API key ID.
Permissions required:
- Quote + order creation: Owner, Admin, Non-Signing Admin, Signer, Editor
- Order reads: all roles including Approver and Viewer
- RFI submission: Owner, Admin, Non-Signing Admin, Signer, Editor