Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions api-reference/fx-payment/operations/create-transfer.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: "Create a transfer"
description: "Moves funds between two accounts of the same customer using a locked quote."
openapi: "apis/fx-payment/openapi.yml POST /api/operations/transfer"
---
290 changes: 288 additions & 2 deletions apis/fx-payment/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,49 @@ components:
example: "PIX_KEY"
required:
- rail
CreateTransferRequest:
type: object
description: >
Same-asset transfer between two accounts of the same customer. The asset on `amount`
must be enabled on both accounts. No FX is performed — `sourceAmount` and `targetAmount`
on the response are equal to `amount`. The system generates a 1:1 quote internally
and returns it on `quote`; callers normally do not provide `quoteId`.
properties:
sourceAccountId:
type: string
format: uuid
description: Source account that will be debited.
example: "a1b2c3d4-5e6f-7890-abcd-ef1234567890"
targetAccountId:
type: string
format: uuid
description: >
Target account that will be credited. Must belong to the same customer as the
source account, must be in `ACTIVE` status, and must have `amount.asset` enabled.
Cannot equal `sourceAccountId`.
example: "b2c3d4e5-6f70-8901-bcde-f12345678901"
amount:
type: object
description: Amount to move. The same value is debited from the source and credited to the target.
properties:
value:
$ref: "#/components/schemas/AmountValue"
asset:
$ref: "#/components/schemas/Asset"
required:
- value
- asset
quoteId:
type: string
format: uuid
description: >
Optional. ID of a quote returned by `POST /api/quotes`. Reserved for future use;
same-asset transfers do not require one and generate a 1:1 quote internally.
example: "d4e5f6a7-b8c9-0123-defa-2345678901bc"
required:
- sourceAccountId
- targetAccountId
- amount
WithdrawalBeneficiary:
description: |
Beneficiary for the withdrawal. Discriminated by `type`.
Expand Down Expand Up @@ -1088,8 +1131,10 @@ components:
type: object
description: >
An operation. Withdrawals (`POST /api/operations/withdrawal`), swaps
(`POST /api/operations/swap`), and deposits (`POST /api/operations/deposit`)
share this shape, discriminated by `intent.type`.
(`POST /api/operations/swap`), deposits (`POST /api/operations/deposit`),
and transfers (`POST /api/operations/transfer`) share this shape, discriminated
by `intent.type`. The `quote` field is always present; for transfers without an
explicit `quoteId` in the request, the system generates a 1:1 same-asset quote.
properties:
id:
type: string
Expand Down Expand Up @@ -1222,12 +1267,14 @@ components:
- $ref: "#/components/schemas/OperationWithdrawalIntent"
- $ref: "#/components/schemas/OperationSwapIntent"
- $ref: "#/components/schemas/OperationDepositIntent"
- $ref: "#/components/schemas/OperationTransferIntent"
discriminator:
propertyName: type
mapping:
WITHDRAWAL: "#/components/schemas/OperationWithdrawalIntent"
SWAP: "#/components/schemas/OperationSwapIntent"
DEPOSIT: "#/components/schemas/OperationDepositIntent"
TRANSFER: "#/components/schemas/OperationTransferIntent"
OperationWithdrawalIntent:
title: Withdrawal
type: object
Expand Down Expand Up @@ -1270,6 +1317,22 @@ components:
required:
- type
- fundingInstruction
OperationTransferIntent:
title: Transfer
type: object
description: >
Intent for a transfer. Carries a snapshot of the target account at the time the
operation was created.
properties:
type:
type: string
enum:
- TRANSFER
targetAccount:
$ref: "#/components/schemas/AccountReference"
required:
- type
- targetAccount
OperationFundingInstruction:
description: Funding instruction the customer uses to send money in. Discriminated by `rail`.
oneOf:
Expand Down Expand Up @@ -2223,6 +2286,15 @@ components:
resource: "Account"
parameters:
id: "a1b2c3d4-5e6f-7890-abcd-ef1234567890"
TargetAccountNotFound:
summary: Target account does not exist or is not owned by the authenticated customer
value:
code: "RESOURCE_NOT_FOUND"
message: "Account with given parameters [id:b2c3d4e5-6f70-8901-bcde-f12345678901] not found"
details:
resource: "Account"
parameters:
id: "b2c3d4e5-6f70-8901-bcde-f12345678901"
InstructionNotFound:
summary: Payment instruction does not exist on the beneficiary
value:
Expand Down Expand Up @@ -2937,6 +3009,220 @@ paths:
quoteAlreadyConsumed:
$ref: "#/components/examples/QuoteAlreadyConsumed"

# ── Operations: Transfers ─────────────────────────────────────────
/api/operations/transfer:
post:
operationId: createTransfer
tags:
- Operations
summary: Create a transfer
description: >
Creates a same-asset transfer that debits one customer account and credits another.
Both accounts must belong to the authenticated customer and must have `amount.asset`
enabled. No FX is performed — the same amount is moved on both legs. The system
generates a 1:1 quote internally and returns it on `quote`; passing `quoteId` is
optional and reserved for future use. Returns `201` immediately with the operation
in `REQUESTED` status; settlement happens asynchronously.

Cross-customer transfers are not supported in the current slice — the target account
must be owned by the same customer as the source account.
parameters:
- $ref: "#/components/parameters/IdempotencyKey"
- $ref: "#/components/parameters/TraceVersion"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateTransferRequest"
examples:
brlTransfer:
summary: BRL transfer between two accounts of the same customer
value:
sourceAccountId: "a1b2c3d4-5e6f-7890-abcd-ef1234567890"
targetAccountId: "b2c3d4e5-6f70-8901-bcde-f12345678901"
amount:
value: "500.00"
asset: "BRL"
responses:
"201":
description: Transfer accepted. The operation is in `REQUESTED` status.
headers:
X-Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/OperationResponse"
examples:
brlTransfer:
summary: BRL transfer in REQUESTED state
value:
id: "4c6d1f10-5b4d-7e6d-cf5b-ae4d7e6dcf5b"
customerId: "11111111-1111-1111-1111-111111111111"
account:
id: "a1b2c3d4-5e6f-7890-abcd-ef1234567890"
owner: "Acme Importação Ltda"
sourceAmount:
value: "500.00"
asset: "BRL"
decimals: 2
targetAmount:
value: "500.00"
asset: "BRL"
decimals: 2
fees: []
transactions: []
tags:
- key: "psp"
value: "Amazon"
- key: "compliance-tier"
value: "high"
currentState:
status: "REQUESTED"
createdAt: "2026-05-06T16:24:11Z"
states:
- status: "REQUESTED"
createdAt: "2026-05-06T16:24:11Z"
intent:
type: "TRANSFER"
targetAccount:
id: "b2c3d4e5-6f70-8901-bcde-f12345678901"
owner: "Acme Importação Ltda"
quote:
id: "d4e5f6a7-b8c9-0123-defa-2345678901bc"
effectiveRate:
value: "1.00"
base: "BRL"
quote: "BRL"
expiresAt: "2026-05-06T16:24:41Z"
consumedAt: "2026-05-06T16:24:11Z"
createdAt: "2026-05-06T16:24:11Z"
updatedAt: "2026-05-06T16:24:11Z"
"400":
description: "Validation error: missing or malformed fields."
headers:
X-Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
missingSourceAccountId:
summary: sourceAccountId is missing
value:
code: "INVALID_DATA"
message: "Object contains invalid data"
details:
errors:
- code: "REQUIRED"
message: "Parameter 'body:sourceAccountId' not found in request"
field: "body:sourceAccountId"
params: {}
missingTargetAccountId:
summary: targetAccountId is missing
value:
code: "INVALID_DATA"
message: "Object contains invalid data"
details:
errors:
- code: "REQUIRED"
message: "Parameter 'body:targetAccountId' not found in request"
field: "body:targetAccountId"
params: {}
invalidSourceAccountUuid:
summary: sourceAccountId is not a UUID
value:
code: "INVALID_DATA"
message: "Object contains invalid data"
details:
errors:
- code: "INVALID_UUID"
message: "'not-a-uuid' is not a valid UUID"
field: "body:sourceAccountId"
params: {}
missingAmount:
summary: amount is missing
value:
code: "INVALID_DATA"
message: "Object contains invalid data"
details:
errors:
- code: "REQUIRED"
message: "Parameter 'body:amount' not found in request"
field: "body:amount"
params: {}
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
description: Source or target account not found for this customer.
headers:
X-Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
sourceAccountNotFound:
$ref: "#/components/examples/AccountNotFound"
targetAccountNotFound:
$ref: "#/components/examples/TargetAccountNotFound"
"409":
description: Idempotency key was reused with a different request body.
headers:
X-Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
idempotencyConflict:
$ref: "#/components/examples/IdempotencyConflict"
"422":
description: Business rule violation.
headers:
X-Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
sameSourceAndTarget:
summary: Source and target accounts are the same
value:
code: "SAME_SOURCE_AND_TARGET"
message: "Source and target accounts must differ"
details:
accountId: "a1b2c3d4-5e6f-7890-abcd-ef1234567890"
targetAccountNotActive:
summary: Target account is not in ACTIVE status
value:
code: "TARGET_ACCOUNT_NOT_ACTIVE"
message: "Target account is not active"
details:
targetAccountId: "b2c3d4e5-6f70-8901-bcde-f12345678901"
status: "OPENING"
sourceAssetNotEnabled:
summary: Requested asset is not enabled on the source account
value:
code: "SOURCE_ASSET_NOT_ENABLED"
message: "Asset is not enabled on the source account"
details:
asset: "USD"
sourceAccountId: "a1b2c3d4-5e6f-7890-abcd-ef1234567890"
targetAssetNotEnabled:
summary: Requested asset is not enabled on the target account
value:
code: "TARGET_ASSET_NOT_ENABLED"
message: "Asset is not enabled on the target account"
details:
asset: "USD"
targetAccountId: "b2c3d4e5-6f70-8901-bcde-f12345678901"

# ── Quotes ────────────────────────────────────────────────────────
/api/quotes:
post:
Expand Down
4 changes: 3 additions & 1 deletion docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@
"journeys/open-multi-currency-account",
"journeys/deposit",
"journeys/withdrawal",
"journeys/swap"
"journeys/swap",
"journeys/transfer"
]
}
]
Expand Down Expand Up @@ -135,6 +136,7 @@
"api-reference/fx-payment/operations/create-withdrawal",
"api-reference/fx-payment/operations/create-swap",
"api-reference/fx-payment/operations/create-deposit",
"api-reference/fx-payment/operations/create-transfer",
"api-reference/fx-payment/operations/get-operation",
"api-reference/fx-payment/operations/list-operations"
]
Expand Down
Loading
Loading