Orders
An order is the actual movement of money: it references a
quotation, names a sender and a recipient, and says where
the money lands. Its status moves through a lifecycle β see
Order status.
create_orderβ
This moves real money. Build the request from a quotation's id, then create the
order with an idempotency key.
Signatureβ
def create_order(
self,
request: CreateOrderRequest,
idempotency_key: Optional[str] = None,
) -> Order:
Parametersβ
| Name | Type | Required | Description |
|---|---|---|---|
request | CreateOrderRequest | Yes | The order to create. |
idempotency_key | Optional[str] | No | Free-form string, not a UUID. Defaults to a generated UUID when omitted β pass your own (e.g. an invoice number) to make retries safe and correlatable. Sent as X-Idempotency-Key and echoed back as idempotency_key. |
CreateOrderRequest fields (build with CreateOrderRequest.with_purpose(...) to pass
purpose as an enum):
| Field | Type | Required | Description |
|---|---|---|---|
quotation_id | str | Yes | Must be a UUID β the id from create_quotation/get_quotation. |
sender | OrderSender | Yes | Who is sending the money. |
sender.names / sender.last_names | str | Yes | Non-empty. |
sender.document | PersonDocument | Yes | type, id (both non-empty), country (2-letter uppercase). |
sender.country | str | Yes | 2-letter uppercase ISO alpha-2. |
sender.phone | Optional[str] | No | E.164 format (e.g. "+584121234567"), validated when not None. Round-trips on reads only when sent on create. |
sender.email | Optional[str] | No | Validated as an email address when not None. Round-trips on reads only when sent on create. |
destination | OrderDestination | Yes | Where the money lands. |
destination.receiver | OrderReceiver | Yes | names, last_names (non-empty), document (as above), country (2-letter uppercase), email (optional, validated if present). |
destination.payment_instructions | PaymentInstructions | Yes | Exactly one of bank_account or bank_phone_account β setting both, or neither, raises. |
destination.payment_instructions.bank_account | Optional[BankAccount] | One of two | Build with BankAccount.of(FinancialEntity, number, BankAccountType). bank_name is validated against the FinancialEntity catalog; type is BankAccountType.CHECKING / SAVINGS / CORRIENTE / AHORRO. |
destination.payment_instructions.bank_phone_account | Optional[BankPhoneAccount] | One of two | Build with BankPhoneAccount.of(FinancialEntity, phone_number, document_id=None). phone_number must be E.164 (e.g. "+584121234567"); document_id falls back to destination.receiver.document.id when omitted. |
purpose | OrderPurpose (via with_purpose) / str | Yes | One of FAMILY_SUPPORT, PAYMENT_OF_BILLS, PURCHASE_OF_GOODS, PROFESSIONAL_SERVICES, RENTAL_OF_SPACES, INVESTMENTS, COMMISSIONS_LOANS_CAPITAL_INCREASE, TRANSPORTATION_COSTS, FEES. |
metadata | Optional[Dict[str, Any]] | No | Free-form key/value pairs echoed back on the order. |
Returnsβ
Order:
| Field | Type | Description |
|---|---|---|
id | Optional[str] | Opaque string, 1..100 chars β not necessarily a UUID. |
type | Optional[str] | Internal movement type, e.g. "WALLET_TO_POG". |
status | Optional[str] | Lifecycle state β see Order status. |
quotation_id | Optional[str] | The quotation this order was built from. |
idempotency_key | Optional[str] | Your idempotency_key, echoed back. |
amount / currency | Optional[str] | The settled amount, as a decimal string. |
metadata | Optional[Dict[str, Any]] | Whatever you sent, echoed back. |
purpose | Optional[str] | Wire value of the declared purpose; use order.purpose_enum to get it back as OrderPurpose. |
sender / destination | Optional[OrderSender] / Optional[OrderDestination] | Echoed back, using the same all-optional models as the request. |
execution | Optional[OrderExecution] | source / destination legs, each an ExecutionLeg (status, reference_id). |
events | Optional[List[OrderEvent]] | Lifecycle transitions β from_status, to_status, trigger, reason, message, created_at. |
failure_code / failure_reason | Optional[str] | Set when status is a failure state β see Failure details. |
created_at / updated_at | Optional[datetime] | Timestamps. |
Exampleβ
import os, uuid
from retorna_sdk import (
CreateOrderRequest, OrderSender, OrderReceiver, PersonDocument,
OrderDestination, PaymentInstructions, BankPhoneAccount,
FinancialEntity, OrderPurpose,
)
# quotation is a QuotationDetail from create_quotation(); client is the RetornaClient
# from the overview's RetornaClient.create(...)
request = CreateOrderRequest.with_purpose(
quotation_id=quotation.id,
sender=OrderSender(names="Ana MarΓa", last_names="PΓ©rez GΓ³mez",
document=PersonDocument(type="CC", id="1020304050", country="CO"),
country="CO"),
destination=OrderDestination(
receiver=OrderReceiver(names="Carlos AndrΓ©s", last_names="RodrΓguez LΓ³pez",
document=PersonDocument(type="V", id="12345678", country="VE"),
payment_instructions=PaymentInstructions.of_bank_phone_account(
BankPhoneAccount.of(FinancialEntity.BNC_VE, "+584121234567", "12345678"))),
purpose=OrderPurpose.FAMILY_SUPPORT,
)
idempotency_key = f"invoice-{uuid.uuid4()}" # free-form str, NOT a uuid.UUID
if os.environ.get("RETORNA_CREATE_ORDER") == "yes":
order = client.create_order(request, idempotency_key)
print(order.id, order.status, order.idempotency_key)
Errorsβ
B2B_QUOTATION_NOT_FOUNDβquotation_iddoesn't exist or isn't yours.B2B_QUOTATION_EXPIREDβ request a new quotation.B2B_QUOTATION_MISMATCHβ amount, currency or corridor doesn't match the quotation.B2B_QUOTATION_ALREADY_USEDβ a quotation is single-use; see the note in the error catalog before retrying anything.B2B_INSUFFICIENT_BALANCEβ top up the wallet first.B2B_BENEFICIARY_INVALIDβ invalid or incomplete payout data for the chosen method.B2B_IDEMPOTENCY_CONFLICTβ the sameidempotency_keywas reused with a different body.B2B_PROVIDER_REJECTED/B2B_PROVIDER_UNAVAILABLEβ the payment network rejected the operation, or is unreachable.B2B_RATE_LIMITEDβ retry with backoff.B2B_UNAUTHORIZEDβ bad token or bad request signature.ValueErrorβ raised locally for a range of missing or invalid fields (see the Parameters table above) before any request goes out β including asender.phonethat is not E.164 or asender.emailthat is not a valid address β cheaper than a round trip for a400.
Notesβ
- Reuse the same
idempotency_keyon retry; the server deduplicates instead of paying twice. - Every nested request model doubles as a response model with all-optional fields, so a partial server response still deserializes.
API referenceβ
list_ordersβ
Lists your company's orders, cursor-paginated, with optional filters.
Signatureβ
def list_orders(self, params: Optional[ListOrdersParams] = None) -> OrdersListResponse:
Parametersβ
ListOrdersParams (all optional; helpers: ListOrdersParams.first_page(limit=...),
ListOrdersParams.by_idempotency_key(idempotency_key)):
| Field | Type | Description |
|---|---|---|
cursor | Optional[str] | Opaque cursor from a previous page's pagination.next_cursor. Omit for the first page β an empty string is invalid. |
limit | Optional[int] | 1 to 100. Fixed for the whole walk once you pass a cursor. |
status | Optional[str] | Filter by order status. |
type | Optional[str] | Filter by transaction type, e.g. "WALLET_TO_POG". Free-form, a single value β unlike status, it does not take a comma-separated list. |
idempotency_key | Optional[str] | Filter by your idempotency key. |
created_from / created_to | Optional[datetime] | Date range filter. |
Returnsβ
OrdersListResponse:
| Field | Type | Description |
|---|---|---|
data | Optional[List[Order]] | The page of orders β see the Order table under create_order. |
pagination | Optional[CursorPagination] | next_cursor, has_more, limit, total (nullable). See Pagination. |
Exampleβ
from retorna_sdk import ListOrdersParams
first = client.list_orders(ListOrdersParams.first_page(limit=50))
by_invoice = client.list_orders(ListOrdersParams.by_idempotency_key("invoice-2026-00456"))
for order in first.data or []:
print(order.id, order.status, order.idempotency_key)
Errorsβ
B2B_INVALID_PAGINATION_CURSORβ the cursor was malformed, tampered with, or not issued by this listing.B2B_UNAUTHORIZEDβ bad token or bad request signature.ValueErrorβ raised locally whenlimitis outside 1..100, orcursor/idempotency_keyis present but blank.
Notesβ
- There is no
pageparameter β offset pagination was removed. Keep filters identical across a paging loop; see Pagination.
API referenceβ
get_orderβ
Fetches a single order by its id.
Signatureβ
def get_order(self, order_id: str) -> Order:
Parametersβ
| Name | Type | Required | Description |
|---|---|---|---|
order_id | str | Yes | Opaque id, 1 to 100 characters β do not assume a UUID shape. |
Returnsβ
Same Order as create_order β see the table above.
Exampleβ
order = client.get_order(order.id)
print(order.id, order.status, order.failure_code)
Errorsβ
B2B_ORDER_NOT_FOUNDβ the id doesn't exist or isn't yours.B2B_UNAUTHORIZEDβ bad token or bad request signature.ValueErrorβ raised locally whenorder_idisNoneor its length is outside 1..100 characters.
Notesβ
- Poll
status(or readevents) to track a payout after creation β see Order status.