Skip to main content

create_transfers

Create one or more Transfers. A successfully created transfer will modify the amount fields of its debit and credit accounts.

Event

The transfer to create. See Transfer for constraints.

Result

Results are listed in this section in order of descending precedence — that is, if more than one error is applicable to the transfer being created, only the result listed first is returned.

ok

The transfer was successfully created; did not previously exist.

Note that ok is generated by the client implementation; the network protocol does not include a result when the transfer was successfully created.

linked_event_failed

The transfer was not created. One or more of the other transfers in the linked chain is invalid, so the whole chain failed.

linked_event_chain_open

The transfer was not created. The Transfer.flags.linked flag was set on the last event in the batch, which is not legal. (flags.linked indicates that the chain continues to the next operation).

imported_event_expected

The transfer was not created. The Transfer.flags.imported was set on the first transfer of the batch, but not all transfers in the batch. Batches cannot mix imported transfers with non-imported transfers.

imported_event_not_expected

The transfer was not created. The Transfer.flags.imported was expected to not be set, as it's not allowed to mix transfers with different imported flag in the same batch. The first transfer determines the entire operation.

timestamp_must_be_zero

This result only applies when Account.flags.imported is not set.

The transfer was not created. The Transfer.timestamp is nonzero, but must be zero. The cluster is responsible for setting this field.

The Transfer.timestamp can only be assigned when creating transfers with Transfer.flags.imported set.

imported_event_timestamp_out_of_range

This result only applies when Transfer.flags.imported is set.

The transfer was not created. The Transfer.timestamp is out of range, but must be a user-defined timestamp greater than 0 and less than 2^63.

imported_event_timestamp_must_not_advance

This result only applies when Transfer.flags.imported is set.

The transfer was not created. The user-defined Transfer.timestamp is greater than the current cluster time, but it must be a past timestamp.

reserved_flag

The transfer was not created. Transfer.flags.reserved is nonzero, but must be zero.

id_must_not_be_zero

The transfer was not created. Transfer.id is zero, which is a reserved value.

id_must_not_be_int_max

The transfer was not created. Transfer.id is 2^128 - 1, which is a reserved value.

exists_with_different_flags

A transfer with the same id already exists, but with different flags.

exists_with_different_pending_id

A transfer with the same id already exists, but with a different pending_id.

exists_with_different_timeout

A transfer with the same id already exists, but with a different timeout.

exists_with_different_debit_account_id

A transfer with the same id already exists, but with a different debit_account_id.

exists_with_different_credit_account_id

A transfer with the same id already exists, but with a different credit_account_id.

exists_with_different_amount

A transfer with the same id already exists, but with a different amount.

If the transfer has flags.balancing_debit or flags.balancing_credit set, then the actual amount transferred exceeds this failed transfer's amount.

exists_with_different_user_data_128

A transfer with the same id already exists, but with a different user_data_128.

exists_with_different_user_data_64

A transfer with the same id already exists, but with a different user_data_64.

exists_with_different_user_data_32

A transfer with the same id already exists, but with a different user_data_32.

exists_with_different_ledger

A transfer with the same id already exists, but with a different ledger.

exists_with_different_code

A transfer with the same id already exists, but with a different code.

exists

A transfer with the same id already exists.

If the transfer has flags.balancing_debit or flags.balancing_credit set, then the existing transfer may have a different amount, limited to the maximum amount of the transfer in the request.

If the transfer has flags.post_pending_transfer set, then the existing transfer may have a different amount:

  • If the original posted amount was less than the pending amount, then the transfer amount must be equal to the posted amount.
  • Otherwise, the transfer amount must be greater than or equal to the pending amount.
Client release < 0.16.0

If the transfer has flags.balancing_debit or flags.balancing_credit set, then the existing transfer may have a different amount, limited to the maximum amount of the transfer in the request.

Otherwise, with the possible exception of the timestamp field, the existing transfer is identical to the transfer in the request.

To correctly recover from application crashes, many applications should handle exists exactly as ok.

id_already_failed

The transfer was not created. A previous transfer with the same id failed due to one of the following transient errors:

Transient errors depend on the database state at a given point in time, and each attempt is uniquely associated with the corresponding Transfer.id. This behavior guarantees that retrying a transfer will not produce a different outcome (either success or failure).

Without this mechanism, a transfer that previously failed could succeed if retried when the underlying state changes (e.g., the target account has sufficient credits).

Note: The application should retry an event only if it was unable to acknowledge the last response (e.g., due to an application restart) or because it is correcting a previously rejected malformed request (e.g., due to an application bug). If the application intends to submit the transfer again even after a transient error, it must generate a new idempotency id.

Client release < 0.16.4

The id is never checked against failed transfers, regardless of the error. Therefore, a transfer that failed due to a transient error could succeed if retried later.

flags_are_mutually_exclusive

The transfer was not created. A transfer cannot be created with the specified combination of Transfer.flags.

Flag compatibility (✓ = compatible, ✗ = mutually exclusive):

debit_account_id_must_not_be_zero

The transfer was not created. Transfer.debit_account_id is zero, but must be a valid account id.

debit_account_id_must_not_be_int_max

The transfer was not created. Transfer.debit_account_id is 2^128 - 1, but must be a valid account id.

credit_account_id_must_not_be_zero

The transfer was not created. Transfer.credit_account_id is zero, but must be a valid account id.

credit_account_id_must_not_be_int_max

The transfer was not created. Transfer.credit_account_id is 2^128 - 1, but must be a valid account id.

accounts_must_be_different

The transfer was not created. Transfer.debit_account_id and Transfer.credit_account_id must not be equal.

That is, an account cannot transfer money to itself.

pending_id_must_be_zero

The transfer was not created. Only post/void transfers can reference a pending transfer.

Either:

pending_id_must_not_be_zero

The transfer was not created. Transfer.flags.post_pending_transfer or Transfer.flags.void_pending_transfer is set, but Transfer.pending_id is zero. A posting or voiding transfer must reference a pending transfer.

pending_id_must_not_be_int_max

The transfer was not created. Transfer.pending_id is 2^128 - 1, which is a reserved value.

pending_id_must_be_different

The transfer was not created. Transfer.pending_id is set to the same id as Transfer.id. Instead it should refer to a different (existing) transfer.

timeout_reserved_for_pending_transfer

The transfer was not created. Transfer.timeout is nonzero, but only pending transfers have nonzero timeouts.

closing_transfer_must_be_pending

The transfer was not created. Transfer.flags.pending is not set, but closing transfers must be two-phase pending transfers.

If either Transfer.flags.closing_debit or Transfer.flags.closing_credit is set, Transfer.flags.pending must also be set.

This ensures that closing transfers are reversible by voiding the pending transfer, and requires that the reversal operation references the corresponding closing transfer, guarding against unexpected interleaving of close/unclose operations.

amount_must_not_be_zero

Deprecated: This error code is only returned to clients prior to release 0.16.0. Since 0.16.0, zero-amount transfers are permitted.

Client release < 0.16.0

The transfer was not created. Transfer.amount is zero, but must be nonzero.

Every transfer must move value. Only posting and voiding transfer amounts may be zero — when zero, they will move the full pending amount.

ledger_must_not_be_zero

The transfer was not created. Transfer.ledger is zero, but must be nonzero.

code_must_not_be_zero

The transfer was not created. Transfer.code is zero, but must be nonzero.

debit_account_not_found

The transfer was not created. Transfer.debit_account_id must refer to an existing Account.

This is a transient error. The Transfer.id associated with this particular attempt will always fail upon retry, even if the underlying issue is resolved. To succeed, a new idempotency id must be submitted.

credit_account_not_found

The transfer was not created. Transfer.credit_account_id must refer to an existing Account.

This is a transient error. The Transfer.id associated with this particular attempt will always fail upon retry, even if the underlying issue is resolved. To succeed, a new idempotency id must be submitted.

accounts_must_have_the_same_ledger

The transfer was not created. The accounts referred to by Transfer.debit_account_id and Transfer.credit_account_id must have an identical ledger.

Currency exchange is implemented with multiple transfers.

transfer_must_have_the_same_ledger_as_accounts

The transfer was not created. The accounts referred to by Transfer.debit_account_id and Transfer.credit_account_id are equivalent, but differ from the Transfer.ledger.

pending_transfer_not_found

The transfer was not created. The transfer referenced by Transfer.pending_id does not exist.

This is a transient error. The Transfer.id associated with this particular attempt will always fail upon retry, even if the underlying issue is resolved. To succeed, a new idempotency id must be submitted.

pending_transfer_not_pending

The transfer was not created. The transfer referenced by Transfer.pending_id exists, but does not have flags.pending set.

pending_transfer_has_different_debit_account_id

The transfer was not created. The transfer referenced by Transfer.pending_id exists, but with a different debit_account_id.

The post/void transfer's debit_account_id must either be 0 or identical to the pending transfer's debit_account_id.

pending_transfer_has_different_credit_account_id

The transfer was not created. The transfer referenced by Transfer.pending_id exists, but with a different credit_account_id.

The post/void transfer's credit_account_id must either be 0 or identical to the pending transfer's credit_account_id.

pending_transfer_has_different_ledger

The transfer was not created. The transfer referenced by Transfer.pending_id exists, but with a different ledger.

The post/void transfer's ledger must either be 0 or identical to the pending transfer's ledger.

pending_transfer_has_different_code

The transfer was not created. The transfer referenced by Transfer.pending_id exists, but with a different code.

The post/void transfer's code must either be 0 or identical to the pending transfer's code.

exceeds_pending_transfer_amount

The transfer was not created. The transfer's amount exceeds the amount of its pending transfer.

pending_transfer_has_different_amount

The transfer was not created. The transfer is attempting to void a pending transfer. The voiding transfer's amount must be either 0 or exactly the amount of the pending transfer.

To partially void a transfer, create a posting transfer with an amount less than the pending transfer's amount.

Client release < 0.16.0

To partially void a transfer, create a posting transfer with an amount between 0 and the pending transfer's amount.

pending_transfer_already_posted

The transfer was not created. The referenced pending transfer was already posted by a post_pending_transfer.

pending_transfer_already_voided

The transfer was not created. The referenced pending transfer was already voided by a void_pending_transfer.

pending_transfer_expired

The transfer was not created. The referenced pending transfer was already voided because its timeout has passed.

imported_event_timestamp_must_not_regress

This result only applies when Transfer.flags.imported is set.

The transfer was not created. The user-defined Transfer.timestamp regressed, but it must be greater than the last timestamp assigned to any Transfer in the cluster and cannot be equal to the timestamp of any existing Account.

imported_event_timestamp_must_postdate_debit_account

This result only applies when Transfer.flags.imported is set.

The transfer was not created. Transfer.debit_account_id must refer to an Account whose timestamp is less than the Transfer.timestamp.

imported_event_timestamp_must_postdate_credit_account

This result only applies when Transfer.flags.imported is set.

The transfer was not created. Transfer.credit_account_id must refer to an Account whose timestamp is less than the Transfer.timestamp.

imported_event_timeout_must_be_zero

This result only applies when Transfer.flags.imported is set.

The transfer was not created. The Transfer.timeout is nonzero, but must be zero.

It's possible to import pending transfers with a user-defined timestamp, but since it's not driven by the cluster clock, it cannot define a timeout for automatic expiration. In those cases, the two-phase post or rollback must be done manually.

debit_account_already_closed

The transfer was not created. Transfer.debit_account_id must refer to an Account whose Account.flags.closed is not already set.

This is a transient error. The Transfer.id associated with this particular attempt will always fail upon retry, even if the underlying issue is resolved. To succeed, a new idempotency id must be submitted.

credit_account_already_closed

The transfer was not created. Transfer.credit_account_id must refer to an Account whose Account.flags.closed is not already set.

This is a transient error. The Transfer.id associated with this particular attempt will always fail upon retry, even if the underlying issue is resolved. To succeed, a new idempotency id must be submitted.

overflows_debits_pending

The transfer was not created. debit_account.debits_pending + transfer.amount would overflow a 128-bit unsigned integer.

overflows_credits_pending

The transfer was not created. credit_account.credits_pending + transfer.amount would overflow a 128-bit unsigned integer.

overflows_debits_posted

The transfer was not created. debit_account.debits_posted + transfer.amount would overflow a 128-bit unsigned integer.

overflows_credits_posted

The transfer was not created. debit_account.credits_posted + transfer.amount would overflow a 128-bit unsigned integer.

overflows_debits

The transfer was not created. debit_account.debits_pending + debit_account.debits_posted + transfer.amount would overflow a 128-bit unsigned integer.

overflows_credits

The transfer was not created. credit_account.credits_pending + credit_account.credits_posted + transfer.amount would overflow a 128-bit unsigned integer.

overflows_timeout

The transfer was not created. transfer.timestamp + (transfer.timeout * 1_000_000_000) would exceed 2^63.

Transfer.timeout is converted to nanoseconds.

This computation uses the Transfer.timestamp value assigned by the replica, not the 0 value sent by the client.

exceeds_credits

The transfer was not created.

The debit account has flags.debits_must_not_exceed_credits set, but debit_account.debits_pending + debit_account.debits_posted + transfer.amount would exceed debit_account.credits_posted.

This is a transient error. The Transfer.id associated with this particular attempt will always fail upon retry, even if the underlying issue is resolved. To succeed, a new idempotency id must be submitted.

Client release < 0.16.0

If flags.balancing_debit is set, then debit_account.debits_pending + debit_account.debits_posted + 1 would exceed debit_account.credits_posted.

exceeds_debits

The transfer was not created.

The credit account has flags.credits_must_not_exceed_debits set, but credit_account.credits_pending + credit_account.credits_posted + transfer.amount would exceed credit_account.debits_posted.

This is a transient error. The Transfer.id associated with this particular attempt will always fail upon retry, even if the underlying issue is resolved. To succeed, a new idempotency id must be submitted.

Client release < 0.16.0

If flags.balancing_credit is set, then credit_account.credits_pending + credit_account.credits_posted + 1 would exceed credit_account.debits_posted.

Client libraries

For language-specific docs see:

Internals

If you're curious and want to learn more, you can find the source code for creating a transfer in src/state_machine.zig. Search for fn create_transfer( and fn execute(.