---
title: "Create an account-to-account transfer"
method: POST
path: "/pty/account/v1/transfers"
tags: ["Ledger API - Transfers"]
---

# Create an account-to-account transfer

`POST /pty/account/v1/transfers`

## Request

- Base URL: `https://api.uat.paymentology.com`
- URL: `https://api.uat.paymentology.com/pty/account/v1/transfers`
- Auth: HTTP bearer

<ul>
<li class="docBullet">Creates an account-to-account transfer.</li>
<li class="docBullet">Business-rule rejections (for example, insufficient funds) are returned as 201 with status REJECTED and failure_reason populated, not as a 4xx error.</li>
</ul>

## Request body

- TransferRequest: Account-to-account transfer request. Both `source_amount` and `destination_amount` are required objects, each carrying its own `currency`. **Exactly one** must supply `amount`; the server computes the other using `exchange_rate` (rounding the result down to the nearest minor unit). When `source_amount.currency == destination_amount.currency`, `exchange_rate` must be absent or `"1"`. When currencies differ, `exchange_rate` is required and follows the convention `destination_amount.amount = source_amount.amount × exchange_rate`.
  - `source_account_id` string, required: Source account identifier
  - `destination_account_id` string, required: Destination account identifier
  - `source_amount` Money, required: A monetary amount with currency. `amount` is in minor units (positive integer string). - On a transfer request, both `source_amount` and `destination_amount` are present and each carries its `currency`. **Exactly one** must supply `amount` — the server computes the other and populates both on the stored record. - On a deposit request (`destination_amount`) and a withdrawal request (`source_amount`), both `amount` and `currency` are always required. - On every response (Create or Get), all `Money` objects are fully populated (`amount` + `currency`).
    - `amount` string: Amount in minor units. Must be greater than 0 (e.g., 2000 = 20.00 USD).
    - `currency` string, required: ISO 4217 currency code
  - `destination_amount` Money, required: A monetary amount with currency. `amount` is in minor units (positive integer string). - On a transfer request, both `source_amount` and `destination_amount` are present and each carries its `currency`. **Exactly one** must supply `amount` — the server computes the other and populates both on the stored record. - On a deposit request (`destination_amount`) and a withdrawal request (`source_amount`), both `amount` and `currency` are always required. - On every response (Create or Get), all `Money` objects are fully populated (`amount` + `currency`).
    - `amount` string: Amount in minor units. Must be greater than 0 (e.g., 2000 = 20.00 USD).
    - `currency` string, required: ISO 4217 currency code
  - `exchange_rate` string: Exchange rate applied when computing the missing amount. Required when `source_amount.currency != destination_amount.currency`; absent or `"1"` otherwise.
  - `fee` TransferFee: Fee on an account-to-account transfer. The fee is additive to the principal: - Fee on source: source account is debited `source_amount.amount + fee.amount`. - Fee on destination: destination account is credited `destination_amount.amount - fee.amount`. `fee.currency` must match the currency of the side the fee is charged to.
    - `amount` string, required: Fee amount in minor units. Must be greater than 0.
    - `currency` string, required: ISO 4217 currency code. Must equal `source_amount.currency` when `charged_to = SOURCE`, and must equal `destination_amount.currency` when `charged_to = DESTINATION`.
    - `charged_to` 'SOURCE' | 'DESTINATION', required: Indicates which side of the transfer pays the fee.
    - `type` string, required: Client-defined fee classification tag
  - `transaction_reference` string, required: Client-provided unique transaction reference (idempotency key). Must not contain whitespace.
  - `transaction_description` string: Human-readable description

## Response `201`

Transfer created

- Transfer: Transfer record. Both `source_amount` and `destination_amount` are fully populated (`amount` + `currency`) — the server fills in whichever side's `amount` was not supplied on the request.
  - `transfer_id` string, uuid, required: Unique transfer identifier.
  - `source_account_id` string, required: Source account identifier.
  - `destination_account_id` string, required: Destination account identifier.
  - `source_amount` Money, required: A monetary amount with currency. `amount` is in minor units (positive integer string). - On a transfer request, both `source_amount` and `destination_amount` are present and each carries its `currency`. **Exactly one** must supply `amount` — the server computes the other and populates both on the stored record. - On a deposit request (`destination_amount`) and a withdrawal request (`source_amount`), both `amount` and `currency` are always required. - On every response (Create or Get), all `Money` objects are fully populated (`amount` + `currency`).
    - `amount` string: Amount in minor units. Must be greater than 0 (e.g., 2000 = 20.00 USD).
    - `currency` string, required: ISO 4217 currency code
  - `destination_amount` Money, required: A monetary amount with currency. `amount` is in minor units (positive integer string). - On a transfer request, both `source_amount` and `destination_amount` are present and each carries its `currency`. **Exactly one** must supply `amount` — the server computes the other and populates both on the stored record. - On a deposit request (`destination_amount`) and a withdrawal request (`source_amount`), both `amount` and `currency` are always required. - On every response (Create or Get), all `Money` objects are fully populated (`amount` + `currency`).
    - `amount` string: Amount in minor units. Must be greater than 0 (e.g., 2000 = 20.00 USD).
    - `currency` string, required: ISO 4217 currency code
  - `exchange_rate` string: Exchange rate applied (present for cross-currency transfers).
  - `fee` TransferFee: Fee on an account-to-account transfer. The fee is additive to the principal: - Fee on source: source account is debited `source_amount.amount + fee.amount`. - Fee on destination: destination account is credited `destination_amount.amount - fee.amount`. `fee.currency` must match the currency of the side the fee is charged to.
    - `amount` string, required: Fee amount in minor units. Must be greater than 0.
    - `currency` string, required: ISO 4217 currency code. Must equal `source_amount.currency` when `charged_to = SOURCE`, and must equal `destination_amount.currency` when `charged_to = DESTINATION`.
    - `charged_to` 'SOURCE' | 'DESTINATION', required: Indicates which side of the transfer pays the fee.
    - `type` string, required: Client-defined fee classification tag
  - `transaction_reference` string, required: Client-provided unique transaction reference.
  - `transaction_description` string: Human-readable description.
  - `status` 'IN_PROGRESS' | 'COMPLETED' | 'REJECTED' | 'FAILED', required: Transaction processing status (shared across transfers, deposits, and withdrawals). - `IN_PROGRESS` — request persisted, operation in progress (non-terminal). - `COMPLETED` — operation executed successfully (terminal). - `REJECTED` — operation refused by a business rule (terminal). The specific reason is carried in `failure_reason.code`. - `FAILED` — operation failed due to a transient error (non-terminal). The specific reason is carried in `failure_reason.code`.
  - `failure_reason` FailureReason: Structured rejection or failure detail. Present iff `status ∈ {REJECTED, FAILED}`; absent on `COMPLETED` and `IN_PROGRESS`. - `REJECTED` codes: `INSUFFICIENT_FUNDS`, `ACCOUNT_LIMIT_EXCEEDED`, `TRANSFER_LIMIT_EXCEEDED`. - `FAILED` codes: `DEPENDENCY_UNAVAILABLE`, `INTERNAL_ERROR`.
    - `code` string, required: Rejection or failure code. Codes currently emitted: - `INSUFFICIENT_FUNDS` - `ACCOUNT_LIMIT_EXCEEDED` - `TRANSFER_LIMIT_EXCEEDED` - `DEPENDENCY_UNAVAILABLE` - `INTERNAL_ERROR` Clients should handle unknown codes gracefully.
    - `message` string, required: Human-readable detail.
  - `updated_at` string, date-time, required: Timestamp of the last update.
  - `source_account_balance_after` string, nullable: The source account's net available balance, in minor units, immediately after this transfer. Present only when `status` is `COMPLETED`.
  - `destination_account_balance_after` string, nullable: The destination account's net available balance, in minor units, immediately after this transfer. Present only when `status` is `COMPLETED`.

## Other responses

- `400`: Request rejected before processing. `VALIDATION_ERROR` for field-level errors (listed in `details`); otherwise a business-rule code: `ACCOUNT_NOT_ACTIVE`, `CURRENCY_MISMATCH`, `INVALID_AMOUNT`, `INVALID_CURRENCY`, `FEE_EXCEEDS_AMOUNT`, `FEE_CURRENCY_MISMATCH`, `SAME_ACCOUNT`, `MISSING_EXCHANGE_RATE`, `INVALID_EXCHANGE_RATE`.
- `401`: Missing or invalid access token: `UNAUTHORIZED`, `INVALID_CLIENT` or `INVALID_TENANT`.
- `403`: Access token rejected by the API gateway (for example, expired).
- `404`: `ACCOUNT_NOT_FOUND`: an account in the request does not exist or does not belong to the client.
- `409`: `IDEMPOTENCY_CONFLICT`: the `transaction_reference` was already used for a transfer with a different request body. Resending the identical request returns the original transfer with `201`.
- `424`: `DEPENDENCY_FAILURE`: the ledger could not process the transfer. It was not completed and can be retried with the same `transaction_reference`.
- `500`: `INTERNAL_SERVER_ERROR`: unexpected error.
- `503`: `SERVICE_UNAVAILABLE` or `INFRASTRUCTURE_ERROR`: temporary failure. The transfer was not completed and can be retried with the same `transaction_reference`.

---

[API](https://skmtc.dev/paymentology/apis/ledger-api.md) - [All operations](https://skmtc.dev/paymentology/apis/ledger-api/llms.txt) - [OpenAPI document](https://skmtc.dev/paymentology/apis/ledger-api/revisions/a5700c501bee?raw)
