Transfer Credit
Move credit from an account you control into one of its direct sub-accounts, without opening the console.
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.
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.
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
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.
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.