Java SDK
A typed client for the Retorna B2B API. It wraps the raw HTTP calls from the quickstart so you stop hand-rolling the OAuth2 token exchange and the per-request signature: the SDK acquires and caches the token for you and signs every outgoing request under the hood. If you want to understand what it is doing on your behalf, read authentication and request signing — the SDK follows those rules exactly, it just saves you from implementing them.
Requirements
- Java 17 or newer.
- Jackson is the only runtime dependency (
jackson-databind+jackson-datatype-jsr310), pulled in transitively — you do not declare it yourself. - No bundled HTTP library. Requests go out over the JDK's built-in
java.net.http.HttpClient, so there is nothing else to pull in.
Install
Maven
<dependency>
<groupId>app.retorna</groupId>
<artifactId>retorna-gateway-sdk</artifactId>
<version>1.0.3</version>
</dependency>
Gradle (Kotlin DSL)
implementation("app.retorna:retorna-gateway-sdk:1.0.3")
Gradle (Groovy DSL)
implementation 'app.retorna:retorna-gateway-sdk:1.0.3'
Configure the client
import app.retorna.sdk.RetornaClient;
import app.retorna.sdk.RetornaClientBuilder;
import app.retorna.sdk.core.environment.RetornaEnvironment;
RetornaClient client = new RetornaClientBuilder()
.environment(RetornaEnvironment.SANDBOX)
.clientId(System.getenv("RETORNA_CLIENT_ID"))
.clientSecret(System.getenv("RETORNA_CLIENT_SECRET"))
.privateKey(System.getenv("RETORNA_PRIVATE_KEY")) // PKCS#8 PEM
.retries(3)
.backoffMs(200L)
.buildClient();
clientId/clientSecret buy you a token; the private key signs every
request. These are two independent checks, so a client built without a key
still gets back a 401 — one that reads exactly like bad credentials, even
though the token was fine. Set all three.
The key must be a PKCS#8 PEM (-----BEGIN PRIVATE KEY-----). Real
newlines, \n-escaped newlines, and the flattened single-line form all parse
— whatever shape your secret manager hands you works. It is decoded once, at
build time, so a malformed key fails immediately instead of on your first
request.
build() is not buildClient()RetornaClientBuilder has two terminal methods. build() only validates your
configuration and returns an SdkConfig — it does not give you a working
client. buildClient() does both: validates and returns the RetornaClient
you actually call. Reach for buildClient().
Environments
| Constant | Base URL |
|---|---|
RetornaEnvironment.SANDBOX | https://api.gateway.sandbox.retorna.app |
RetornaEnvironment.PRODUCTION | https://api.gateway.retorna.app |
RetornaEnvironment.DEVELOP | Internal use only — no public base URL. |
Picking an environment is the only configuration step: the SDK resolves the right base URL for you, so nothing else in your code changes between sandbox and production besides the enum value and your credentials.
Sub-clients
Every method shown below is also reachable through a domain-scoped sub-client, and both paths call the same code:
client.createQuotation(request); // flat method
client.quotations().createQuotation(request); // same call, through the sub-client
RetornaClient exposes one accessor per domain object: quotations(), orders(),
wallets(), routes(), clients(). Reach for the sub-client when you are passing the
domain client around as its own dependency (a QuotationsClient field, for example)
instead of the whole RetornaClient; otherwise the flat methods read just as well.
First call
A quick way to confirm your credentials and signature are both correct before touching money movement:
ClientResponse me = client.getMyClient();
System.out.printf("%s (active=%s)%n", me.client().name(), me.client().isActive());
If this throws, jump to Handling errors below — the exception tells you whether it was your token or your signature.
Quotation → order
Every payout starts with a quotation, then an order that references it:
QuotationDetail quote = client.createQuotation(new CreateQuotationRequest(
new QuotationSource("USDR"),
new QuotationDestination("VE", "VES",
new QuotationPayoutMethod(PayoutMethodType.P2P_PHONE_TRANSFER)),
new QuotationQuote(QuoteMode.SEND_EXACT, "10.00")));
System.out.printf("rate=%s, receiver gets %s %s%n",
quote.exchangeRate().value(), quote.target().amount(), quote.target().currency());
String idempotencyKey = "invoice-2026-0042"; // your own reference
Order order = client.createOrder(
CreateOrderRequest.withPurpose(
quote.id(),
null, // metadata, echoed back untouched
OrderPurpose.FAMILY_SUPPORT, // required, closed catalog
new OrderSender("Ana María", "Pérez Gómez",
new PersonDocument("CC", "1020304050", "CO"), "CO"),
new OrderDestination(
new OrderReceiver("Carlos Andrés", "Rodríguez López",
new PersonDocument("V", "12345678", "VE"),
"[email protected]", "VE"),
PaymentInstructions.ofBankPhoneAccount(
BankPhoneAccount.of(FinancialEntity.BNC_VE,
"+584121234567", "12345678")))),
idempotencyKey);
System.out.printf("Order %s status=%s idempotencyKey=%s%n",
order.id(), order.status(), order.idempotencyKey());
A quotation is single-use: it backs exactly one order, forever. Pass a real
idempotency key on createOrder — an invoice
number beats a random UUID, because it round-trips as order.idempotencyKey()
and lets you find this order later. Reuse the same key on a retry; the server
deduplicates for you. There is no idempotencyKey field on the request itself —
only on the response — so do not try to set one.
The bank account's bankName comes from a closed catalog: FinancialEntity
constants like BANCO_BANESCO_VE, never the bank's commercial name. See the
full list under bank entities.
Objects
Each domain object has its own page with every method, its parameters, its return shape, and the errors it can raise:
- Quotations — price a corridor and payout method before
committing to a transfer:
createQuotation,getQuotation. - Orders — create, read, and list the payouts themselves:
createOrder,getOrder,listOrders. - Routes — the corridors your company is configured to send
through, with their rates, fees, and limits:
getRoutes. - Wallets — your company's balance and top-up history:
getMyWallet,getMyWalletTransactions. - Client — identity and permissions of the authenticated
client:
getMyClient.
Handling errors
The SDK throws one of three exception types, all subclasses of
RetornaException:
try {
Order order = client.createOrder(request, idempotencyKey);
} catch (RetornaB2BException e) {
if (e.codeEnum().isRetryable()) {
// TRANSIENT — retry with the SAME idempotency key
} else {
System.err.printf("%s [%s, http=%d] %s (correlationId=%s)%n",
e.code(), e.category(), e.httpStatus(), e.getMessage(), e.correlationId());
}
} catch (RetornaAuthException e) {
System.err.printf("Auth failed (HTTP %d): %s%n", e.httpStatus(), e.getMessage());
} catch (RetornaSdkException e) {
System.err.printf("SDK error in %s: %s%n", e.context(), e.getMessage());
}
Branch on code() — the raw wire string — rather than getMessage(), which
is written for humans and can change. codeEnum() degrades gracefully to an
unknown, non-retryable value when the backend adds a code this SDK version
does not know about yet, so your branching never breaks on an upgrade.
This page does not repeat the error catalog. See error codes for the full list of codes, statuses and categories, and handling errors for how the different failure shapes relate to each other.
One client, many threads
Build a single RetornaClient when your application starts and reuse it for
every request — do not construct one per call. Under the hood it keeps
exactly one java.net.http.HttpClient instance, built once at construction
time and reused for every outgoing call. It is safe to share: the
token cache refreshes itself safely under concurrent access, and signing a
request touches no shared mutable state. RetornaClient implements
AutoCloseable, so try-with-resources works, though closing it today has
nothing to release.
Every outgoing request also carries an X-Retorna-Client header identifying
the SDK and its version — useful context if you ever need to reference a
request in a support conversation.
Where to next
- Authentication — what the SDK is doing under the hood for the token exchange.
- Request signing — the signature rules the SDK applies to every call.
- Idempotency — the full rules behind the
createOrderkey. - Pagination — the cursor contract shared by every paginated listing.
- Error codes and Handling errors — the complete error reference.