SDK de Python
Un cliente tipado y fluido para la API B2B de Retorna: crea una cotización, conviértela en una orden, revisa tu wallet y lista las rutas habilitadas para tu compañía — sin construir requests HTTP ni firmar nada a mano. Está fijado a la versión 1.0.4, y esta página sigue esa versión.
Instalación
pip install retorna-gateway-sdk
# or
uv add retorna-gateway-sdk
# or
poetry add retorna-gateway-sdk
Necesitas Python 3.9+. La única dependencia de runtime es cryptography, usada
para parsear la clave RSA y firmar los requests — todo lo demás es librería estándar.
El paquete se llama retorna-gateway-sdk; el módulo que importas es retorna_sdk.
Configurar el cliente
Todos los ejemplos de esta página corren contra sandbox. Crea el cliente una sola vez y reutilízalo — guarda tus credenciales y un pool de conexiones, no un request puntual.
import os
from retorna_sdk import RetornaClient
client = RetornaClient.create(
environment="SANDBOX",
client_id=os.environ["RETORNA_CLIENT_ID"],
client_secret=os.environ["RETORNA_CLIENT_SECRET"],
private_key=os.environ["RETORNA_PRIVATE_KEY"], # PKCS#8 PEM
)
También existe un builder, si prefieres construir la configuración paso a paso —
RetornaClientBuilder().environment("SANDBOX")...build_client() te da el mismo
cliente. Presta atención al nombre: build_client() devuelve un RetornaClient listo
para usar; el más corto build() solo devuelve el objeto de configuración resuelto,
no un cliente que puedas invocar.
create() acepta estos argumentos:
| Opción | Default | Notas |
|---|---|---|
environment | PRODUCTION | Configúralo explícitamente — el default no es sandbox |
scope | default del ambiente | Scope de OAuth2, rara vez se sobreescribe |
logging_level | ERROR | Verbosidad del logging interno del SDK |
retries | 3 | Reintentos automáticos ante fallas transitorias |
backoff_ms | 200 | Delay base entre reintentos |
connect_timeout | 10.0 | Segundos |
request_timeout | 10.0 | Segundos |
ssl_context | ninguno | ssl.SSLContext personalizado, para configuraciones TLS avanzadas |
base_url_override | ninguno | Apunta a un host no estándar, por ejemplo para testing local |
Ambientes
| Ambiente | URL base |
|---|---|
DEVELOP | https://api.gateway.dev.retorna.app |
SANDBOX | https://api.gateway.sandbox.retorna.app |
PRODUCTION | https://api.gateway.retorna.app |
Tu primer llamado
Confirma que el cliente quedó bien configurado antes de hacer cualquier otra cosa:
me = client.get_my_client()
print(me.client.name, me.client.is_active, me.client.permissions)
Cotización → orden
Una cotización fija un tipo de cambio por una ventana corta; referencia su id al
crear la orden. La documentación completa de cada método — todos los campos, valores
de retorno y códigos de error — vive en las páginas de
Cotizaciones y Órdenes; aquí va la
forma de todo el flujo en un solo ejemplo:
import os, uuid
from retorna_sdk import (
CreateQuotationRequest, QuotationSource, QuotationDestination,
QuotationPayoutMethod, QuotationQuote, QuoteMode, PayoutMethodType,
CreateOrderRequest, OrderSender, OrderReceiver, PersonDocument,
OrderDestination, PaymentInstructions, BankPhoneAccount,
FinancialEntity, OrderPurpose,
)
quotation = client.create_quotation(CreateQuotationRequest(
source=QuotationSource(currency="USDR"),
destination=QuotationDestination(
country="VE", currency="VES",
payout_method=QuotationPayoutMethod(type=PayoutMethodType.P2P_PHONE_TRANSFER)),
quote=QuotationQuote(mode=QuoteMode.SEND_EXACT, amount="56.00"),
))
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)
QuoteMode.SEND_EXACT significa que quien envía se desprende de exactamente 56.00;
RECEIVE_EXACT describe lo que recibe el destinatario en cambio. idempotency_key es
un str común, no un tipo UUID, y vuelve como idempotency_key en la orden — si
reutilizas esa key con un body distinto, la respuesta es B2B_IDEMPOTENCY_CONFLICT
(409) en vez de la orden original. La verificación de RETORNA_CREATE_ORDER de arriba
solo está para que este snippet se pueda copiar y ejecutar sin mover dinero real por
accidente — quítalo de tu propio código cuando estés listo.
Objetos
Cotizaciones — fijan un tipo de cambio, comisiones y montos antes de crear una
orden. Consulta Cotizaciones para create_quotation y
get_quotation.
Órdenes — el movimiento real de dinero, creado a partir de una cotización. Consulta
Órdenes para create_order, list_orders y get_order.
Rutas — los corredores (país, moneda y método de pago de destino) habilitados para
tu compañía. Consulta Rutas para get_routes.
Wallets — el balance de tu compañía y su historial de recargas. Consulta
Wallets para get_my_wallet y get_my_wallet_transactions.
Cliente — a quién pertenecen las credenciales. Consulta Cliente
para get_my_client.
Errores
Todo error que lanza el SDK es una de tres clases, todas terminadas en Error — no
Exception — y todas importables desde el paquete de nivel superior. RetornaAuthError,
RetornaB2BError y RetornaSdkError extienden todas de RetornaError, así que un
solo except RetornaError atrapa todo lo que lanza el SDK:
from retorna_sdk import RetornaAuthError, RetornaB2BError, RetornaSdkError
try:
order = client.create_order(request, idempotency_key)
except RetornaB2BError as e:
if e.code == "B2B_IDEMPOTENCY_CONFLICT":
... # same key, different payload
elif e.is_retryable:
... # a TRANSIENT category error
print(e.code, e.code_enum, e.http_status, e.correlation_id)
except RetornaAuthError as e:
... # token OR signature — the API never says which
except RetornaSdkError as e:
print(e.context, e.cause) # transport / JSON / unstructured 5xx
RetornaB2BError lleva el code de la API tal cual, como string, más code_enum
para hacer coincidencias exhaustivas e is_retryable para decidir rápido si conviene
reintentar. Para la lista completa de códigos y qué significa cada uno, consulta el
catálogo de errores y la
guía de manejo de errores — esta página no duplica esa tabla a
propósito.
Trampas comunes
private_key tiene que ser un PEM PKCS#8 (-----BEGIN PRIVATE KEY-----). Una clave
PKCS#1 (-----BEGIN RSA PRIVATE KEY-----) falla inmediatamente con un error accionable
al construir el cliente — conviértela primero:
openssl pkcs8 -topk8 -nocrypt -in key.pem -out key-pkcs8.pem
Los montos siempre son str en la API — "56.00", nunca 56.0. No los coloques en un
float: IEEE-754 no puede representar 0.1 exactamente, y un error de redondeo en un
monto de pago es un bug real. Usa retorna_sdk.core.money.parse / .format cuando
necesites hacer aritmética real, y vuelve a str antes de enviar el valor.
Un bearer token inválido y una firma de request inválida vuelven como el mismo
RetornaAuthError con un 401 escueto. Ni el SDK ni la API te dicen cuál de los dos
falló. Si te pasa esto, revisa ambos por separado en vez de asumir que siempre es el
token.
Próximos pasos
- Autenticación — cómo encajan el token y la firma
- Firma de requests — los bytes exactos que se firman
- Idempotencia — reintentos seguros para
create_order - Paginación — cursores en órdenes y transacciones de wallet
- Manejo de errores — las cuatro formas de respuesta que puedes recibir
- Catálogo de errores — cada
codey si admite reintento - Tipos de payout — transferencia bancaria vs. transferencia por teléfono
- USDR — la moneda usada a lo largo de esta página
- Entidades bancarias — Venezuela — el catálogo
completo de
FinancialEntity - Referencia de la API de órdenes