Skip to main content
A wallet is where your customer’s stablecoin actually sits. Endl custodies the keys; you never handle them, and there is no seed phrase to show anyone. What you get back is an address on one network, and that address is the thing you give to whoever is sending crypto.
A wallet is bound to exactly one network. There is no multi-chain wallet and no single address that works everywhere. A customer who needs to receive USDC on Base and on Ethereum needs two wallets.
This is the conceptual guide. For the endpoints themselves — parameters, schemas and worked bodies — see the Wallets API reference.

What a wallet is for

Two things, and they pull in different directions.

Receiving — the address

Somebody sends stablecoin to the wallet’s address on its network. That is an ordinary on-chain transfer; nothing about it is Endl-specific.

Settling — the destination

An ONRAMP account converts a fiat deposit and settles the stablecoin into a wallet. That is why walletId is required when you open one — the wallet supplies the settlement asset, chain and address.
The second is the common case. Most partners create one wallet per customer per network and then point every ONRAMP account at it.

Who may create one

The customer must have completed KYC. Create and create-on-network are both gated on it; everything else — list, get, balance, update, delete — is not. An incomplete customer gets 403 ERRWLT_1005, and the check is the same completion rule the rest of the platform uses, so a customer who can transact can create a wallet.
The gate is on completion, not on having started. A customer sitting in PENDING or UNDER_REVIEW fails it exactly like one who never began. Create the customer and get them through verification before you try to open a wallet for them.

Networks

Fifteen networks are supported platform-wide: EVM mainnets, their testnets, TRON and XRP.
network is matched case-sensitively when you create a wallet. Base is accepted; base and BASE are rejected with network must be one of: …. Copy the spelling out of the table above exactly.Confusingly, the same names are matched case-insensitively everywhere else — reading a balance for :chain, naming a network on a transfer. Only creation is strict. Treat the canonical casing as the only correct form and the inconsistency never bites you.
An environment can expose fewer networks than this table. Sandbox and QA are pointed at test networks and restrict creation to that subset, so a Base wallet that works in production is refused there. The error message lists the networks the environment you are calling actually allows — read it rather than assuming the full set.
Quotes spell these networks differently — see the mapping table.

Creating a wallet

1

Create the customer and get them verified

Wallets hang off a customer. Create one, keep its cus_… reference id — that is the {userId} in every wallets path — and see them through KYC.
2

POST the network

network is the only required field. name is optional but must be a non-empty string if you send it at all, and tags must be an array of strings if present.
Request
A userId in the body is stripped and ignored — the customer is the one in the path, and only the one in the path.
3

Read the id and address off the response

The response carries the wallet’s walletId (a wlt_… reference), its address, its network and a status. The address is live from that moment; there is no activation step.
4

Add further networks, if you need them

Create wallet on network takes an existing wallet and derives another wallet on a different network from the same underlying key material. It is not a mutation of the wallet you name. The response is a new wallet with its own wlt_ id and its own address — the networks are related cryptographically, not merged into one object.

What comes back

currency is not a restriction. Every wallet is recorded locally with USDC, because that is the default the record is stamped with at creation. It does not mean the wallet can only hold USDC, and it is not derived from what is actually on-chain. For what the wallet really holds, call Get wallet balance.
The cryptographic material behind the wallet — public key, scheme, curve — is deliberately not in the response. If you are looking for a signingKey block because you saw one in a custody provider’s own docs, it is removed on purpose and will not be added.

The primary wallet

One wallet per customer can be flagged isPrimary. The rule is narrow and worth knowing exactly:
  • It is set only on customers created through the partner API.
  • It is set only on the first wallet, when the customer has no active primary wallet yet.
  • It is only ever granted, never revoked. Creating a second wallet does not move the flag, and re-syncing an existing wallet does not clear it.
Treat it as “the first wallet we made for this customer”, not as a setting you control — there is no endpoint to move it.

Reading balances

Get wallet balance returns the wallet’s native asset, its tokens and a USD total. For most networks the call aggregates across chains in one request. TRON is the exception: TRON balances are queried on their own chain because they are not covered by the aggregate. You do not have to do anything about this — Endl picks the right query from the wallet’s network — but it does mean a TRON wallet’s balance reflects TRON only, while an EVM wallet’s can span the networks its key covers.
The balance body names the wallet under walletId, and that value is rewritten to your wlt_… reference before it reaches you. Everything else in the body — address, chain, native, tokens, totalUsd — is on-chain data and passes through untouched.

Updating and deleting

Update takes name, externalId, or both. Sending neither is a 400 — a no-op update is treated as a mistake rather than quietly accepted. Only name is mirrored into Endl’s own record; externalId lives at the custody layer. Delete is soft, and it is one-way through this API.
A deleted wallet does not vanish, and deleting it does not move any funds.
  • It stops appearing in List wallets.
  • Get wallet returns 404 ERRWLT_1000, even though the record still exists.
  • The response is 200 with status: "deleted" — not an empty 204.
Sweep any balance out before you delete. There is no undelete endpoint.

Scoping and reference ids

Every wallets path is /{userId}/…, and the customer is resolved from that path segment alone. A wallet that belongs to a different customer is reported as 404 ERRWLT_1000 — never 403 — so a partner can never probe which wallet ids exist under another customer. Raw UUIDs are not accepted in either position.

Error handling

Wallet-specific failures carry an ERRWLT_ code. Everything cross-cutting — validation, auth, rate limiting — uses the shared ERRCORE_ codes described in Errors and the response envelope. None of these are retryable. Branch on the code, never on the message text — the codes are frozen and will never be renumbered or reused; the wording may be refined at any time.

Webhooks

Wallets produce two events. Delivery, signing, replay protection and the envelope shape are covered in the Webhooks guide — this is only what is specific to wallets. data.id is the wallet’s wlt_… reference. The payload body is deliberately thin — network and currency, nothing else.
Both events are best-effort, and both are emitted after the work has already succeeded. If the emit fails the wallet still exists (or is still deleted) — the API response is the authority, not the event.
There is also no wallet.updated. Renaming a wallet emits nothing, and so does money arriving: an incoming on-chain transfer is a deposit event, not a wallet event.

Common pitfalls

Check the casing. Creation matches network exactly: Base, not base. Everything else in the API is case-insensitive about network names, which is what makes this one easy to miss.
Sandbox and QA restrict creation to test networks. The error lists the networks that environment allows — BaseSepolia rather than Base.
ERRWLT_1005 is the KYC gate, not a permission failure. The customer has not completed verification. A permission failure is ERRCORE_1005.
currency is a local default stamped at creation, not a reading of the chain and not a restriction on the wallet. Use Get wallet balance for holdings.
That is correct. It creates an additional wallet on another network from the same key material and returns that new wallet, with its own wlt_ id and address. The wallet you named in the path is untouched.
Three possibilities, all reported identically on purpose: it was deleted, it belongs to a different customer, or the wlt_ reference is well-formed but unknown. Check List wallets for that customer.
Delete returns a body — 200 with status: "deleted". Nothing in the v0 surface returns an empty 204.
The delete succeeded and the funds did not move. Deletion is a bookkeeping operation, not a sweep. Contact Endl — there is no undelete endpoint.
There isn’t one. A wallet is one network, one address. Create one per network you need to receive on, and show the payer the address matching the network they are sending from.

Glossary

Written against Api-Version: 2026-09.1. The network list is platform-wide; what a given environment and a given customer can actually use is narrower, so read errors rather than hardcoding this page.