Skip to content
External Console API

Transfer Credit

Move credit from an account you control into one of its direct sub-accounts, without opening the console.

POST https://console-api.siraya.ai/extapi/v1/accounts/transfers

Body Parameters

Both sides of the transfer are named in the body. There is no path parameter and no query parameter.

accountIdFrom string

The account the funds leave. Must be a UUID when present. Omit it and the first account from List Accounts is used — if your token reaches more than one account, always pass it, because that default is positional and moves as your account list changes.

accountIdTo string Required
The account the funds arrive in. Must be a UUID, and a direct sub-account of the paying account.
amount number Required
The sum to move. Must be greater than zero.
curl -X POST https://console-api.siraya.ai/extapi/v1/accounts/transfers \
  -H "Authorization: Bearer <CONSOLE_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "accountIdFrom": "11111111-2222-3333-4444-555555555555",
    "accountIdTo": "66666666-7777-8888-9999-000000000000",
    "amount": 10.5
  }'
import requests

url = "https://console-api.siraya.ai/extapi/v1/accounts/transfers"
headers = {
    "Authorization": "Bearer <CONSOLE_TOKEN>",
    "Content-Type": "application/json",
}
payload = {
    "accountIdFrom": "11111111-2222-3333-4444-555555555555",
    "accountIdTo": "66666666-7777-8888-9999-000000000000",
    "amount": 10.5,
}

response = requests.post(url, headers=headers, json=payload)
print(response.json()["data"]["transferId"])

Example Response

{
  "isSuccess": true,
  "data": {
    "transferId": "tr_01H8ZQ4M7N",
    "amount": "10.5"
  }
}

Reading the response

Field Meaning
transferId The identifier this transfer was given. It is the only identifier a transfer produces — record it, and quote it when asking about the transfer later. Omitted rather than returned empty
amount The sum that moved, as a decimal string. Echoed from your request

The response does not carry either side's resulting balance. Read balances separately.

Who may send

The sending account — accountIdFrom — must hold a wallet of its own, which means either a root account or a direct_transfer sub-account, and it must not be archived.

A limit_quota sub-account is refused. It holds no balance of its own — its spending is debited from the nearest direct_transfer ancestor — so it has nothing to send.

Who may receive

The receiving account — accountIdTo — must be a direct child of the sender, one level down, and must itself be direct_transfer and not archived.

DEPTH 0 DEPTH 1 DEPTH 2 CAN SEND A → A · NO Root top of hierarchy SENDER A initiates transfer NO B different branch OK A1 direct child OK A2 direct child Sender Allowed target Rejected target Permitted send Parent to child Self-send
A transfer is permitted only exactly one level down from the sender. B sits in another branch and the sender is zero levels from itself — both are refused.

Transfers to a grandchild are refused deliberately: fund A1 from A, then A11 from A1, so each step is authorised by the account whose money actually moves.

Errors

Status error.code Meaning
400 BAD_REQUEST accountIdTo missing or not a UUID; accountIdFrom present but not a UUID; amount missing, zero or negative; sending to yourself; receiver is a direct child but is limit_quota
400 IAM_NOT_DIRECT_SUB_ACCOUNT Receiver is inside your tree but deeper than one level
400 SUB_ACCOUNT_ARCHIVED Sending or receiving account is archived
401 UNAUTHORIZED / INVALID_CREDENTIALS Token missing, malformed or revoked
403 FORBIDDEN Token lacks res.credit; membership inactive; or the sending account is a limit_quota sub-account
404 SUB_ACCOUNT_NOT_FOUND The receiver is not reachable from the sending account
429 RATE_LIMIT_EXCEEDED Per-user rate limit
500 INTERNAL_ERROR Account lookup, ancestry resolution, or the transfer itself failed

Why 404 is deliberately vague

A receiver that does not exist, one that is a root account, and one belonging to another organisation all return the same 404, identical in status and body. The endpoint therefore cannot be used to discover which account ids are real by reading the errors apart.

Rejections for an account you already own are specific, because naming the reason tells you nothing you could not already see in the console.