POST
/strings/{stringCode}/convertPost an atomic different-currency conversion between two member ledgers of a string
Moves value from a customer account in the source member ledger to the target member ledger in a DIFFERENT currency. Both source_amount and target_amount are authoritative; rate is recorded for audit only. BOTH legs post atomically. Requires an api key whose ledger is a member of the string and which carries the strings:participate:{stringCode} capability. Supply a first-class idempotency key via the Idempotency-Key header (or an idempotency_key body field); replaying it returns the original result without double-posting.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
stringCoderequired | path | string | String code (unique within the tenant). |
Idempotency-Key | header | string max length: 255 | First-class idempotency key (canonical). Falls back to the body idempotency_key when absent. |
Request bodyrequired
| Field | Type | Description |
|---|---|---|
source_ledger_coderequired | string | Code of the member ledger value leaves. |
target_ledger_coderequired | string | Code of the member ledger value arrives in (must differ from the source and use a different currency). |
account_coderequired | string max length: 36 | The logical customer account, present across the string. |
source_amountrequired | string | Authoritative amount leaving the source account, in the source currency (major units). |
target_amountrequired | string | Authoritative amount arriving in the target account, in the target currency (major units). |
rate | string nullable | The conversion rate, recorded for audit only (never used to recompute either amount). OPTIONAL: when omitted, a live rate is resolved from the FX rate service and snapshotted before posting; an explicit rate stays authoritative. |
referencerequired | string max length: 100 | The per-ledger transaction reference stamped on both legs. |
effective_date | string date · nullable | Posting effective date (Y-m-d). Defaults to today when omitted. |
description | string max length: 255 · nullable | Optional human description recorded on both legs. |
attributes | object nullable | Optional searchable business dimensions — a flat map of key => value string pairs (e.g. {"customer_id": "C-123", "loan_id": "L-42"}) stamped on BOTH legs (each leg is independently searchable). Keys must be lowercase snake_case (max 64 chars) and not shadow a reserved field; values are strings (max 128 chars). Subject to per-transaction (default 20) and per-tenant distinct-key caps, and, when the tenant runs strict mode, must be declared in the attribute catalog first — a violation returns 422 with an ATTRIBUTE_* code synchronously at the gate. Written atomically with both legs and immutable thereafter. |
idempotency_key | string max length: 255 · nullable | Body fallback for the idempotency key when the Idempotency-Key header is not sent. |
Responses
200Idempotent replay: the original result is returned
{
"success": true,
"data": {
"operation": "transfer",
"string_code": "treasury-fx",
"correlation_id": "01997c1e-4d2a-7c3e-9a1b-2f0e8c6d4b5a",
"replayed": false,
"source": {
"ledger_code": "usd-main",
"ledger_id": "01997c1e-4d2a-7c3e-9a1b-2f0e8c6d4b5a",
"transaction_id": "01997c1e-4d2a-7c3e-9a1b-2f0e8c6d4b5a",
"reference": "FX-2026-0001",
"amount": "100.00"
},
"target": {
"ledger_code": "eur-main",
"ledger_id": "01997c1e-4d2a-7c3e-9a1b-2f0e8c6d4b5a",
"transaction_id": "01997c1e-4d2a-7c3e-9a1b-2f0e8c6d4b5a",
"reference": "FX-2026-0001",
"amount": "92.00"
},
"rate": "0.92"
}
} 201Conversion posted (both legs committed)
{
"success": true,
"data": {
"operation": "transfer",
"string_code": "treasury-fx",
"correlation_id": "01997c1e-4d2a-7c3e-9a1b-2f0e8c6d4b5a",
"replayed": false,
"source": {
"ledger_code": "usd-main",
"ledger_id": "01997c1e-4d2a-7c3e-9a1b-2f0e8c6d4b5a",
"transaction_id": "01997c1e-4d2a-7c3e-9a1b-2f0e8c6d4b5a",
"reference": "FX-2026-0001",
"amount": "100.00"
},
"target": {
"ledger_code": "eur-main",
"ledger_id": "01997c1e-4d2a-7c3e-9a1b-2f0e8c6d4b5a",
"transaction_id": "01997c1e-4d2a-7c3e-9a1b-2f0e8c6d4b5a",
"reference": "FX-2026-0001",
"amount": "92.00"
},
"rate": "0.92"
}
} 401Unauthenticated
{
"success": false,
"message": "Validation failed",
"code": "VALIDATION_ERROR"
} 403Forbidden (STRING_KEY_NOT_IN_STRING / STRING_KEY_MISSING_CAPABILITY / STRING_FOREIGN_LEDGER / STRING_OP_NOT_ALLOWED)
{
"success": false,
"message": "Validation failed",
"code": "VALIDATION_ERROR"
} 409Conflict (STRING_PERIOD_LOCKED / STRING_INACTIVE / STRING_NOT_FOUND)
{
"success": false,
"message": "Validation failed",
"code": "VALIDATION_ERROR"
} 422Validation error (STRING_CURRENCY_MISMATCH / STRING_OPERATION_INVALID)
{
"success": false,
"message": "Validation failed",
"code": "VALIDATION_ERROR"
} Examples
curl -X POST https://ledga.io/api/v1/strings/{stringCode}/convert \
-H "Authorization: Bearer sk_xxxxxxxx_your_secret" \
-H "Content-Type: application/json" \
-d '{
"source_ledger_code": "usd-main",
"target_ledger_code": "eur-main",
"account_code": "1000",
"source_amount": "100.00",
"target_amount": "92.00",
"rate": "0.92",
"reference": "FX-2026-0001",
"effective_date": "2026-06-30",
"description": "USD→EUR conversion",
"attributes": {
"customer_id": "C-123",
"loan_id": "L-42"
},
"idempotency_key": "convert-2026-0001"
}'