Ledga
POST/strings/{stringCode}/convert

Post 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

NameInTypeDescription
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

FieldTypeDescription
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"
}'

← All endpoints