Quickstart

From credentials to a confirmed transfer, in six calls.
View as MarkdownOpen in Claude

1. Get credentials and pick an environment

Start by asking your service operator for sandbox credentials — a client id and an api key. The base URLs below are public, but nothing works without those two values: every endpoint needs a token, and only your operator can issue the credentials that mint one.

Base URL
Sandbox — develop and test herehttps://api-demo.walletstech.com
Production — real fundshttps://api.walletstech.com

This guide uses sandbox. It ends by broadcasting a real transfer, so run it there first.

export BASE_URL="https://api-demo.walletstech.com"
export CLIENT_ID="<your client id>"
export API_KEY="<your api key>"

Keep the credentials in configuration or a secret store — never hard-coded in source, never in a public repo. Every example below reads them from the environment.

Credentials are per environment. A sandbox client id and api key will not authenticate against production; going live means requesting a second set and changing BASE_URL. Nothing else changes — the paths, request bodies, headers and responses are identical on both.

2. Get a token

Your client id and api key go in headers, not the body. Send the request with an empty body.

curl -X POST "$BASE_URL/auth" \
-H "x-client-id: $CLIENT_ID" \
-H "x-api-key: $API_KEY"
{
"result": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresAt": "2026-07-13T10:15:00.000Z",
"refreshToken": "5d41402abc4b2a76b9719d911017c592a1b2c3d4e5f60718293a4b5c6d7e8f90",
"refreshExpiresAt": "2026-07-20T10:00:00.000Z"
},
"requestId": "b7a1f0c2-3d4e-4a5b-9c6d-7e8f90a1b2c3"
}

The token lasts 15 minutes, and every call below sends it as Authorization: Bearer <token>. When it expires, post the refreshToken to /refresh for a fresh pair rather than calling /auth again — see Authentication.

Refresh tokens are single-use: each /refresh consumes the one you send and returns a replacement, so always store the new one.

3. Create a user

One call mints the user and derives a wallet on every configured chain.

curl -X POST "$BASE_URL/create" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "password": "a-strong-password" }'
{
"result": {
"userId": "66b1f0c23d4e4a5b9c6d7e8f",
"mnemonic": "<your 12-word seed phrase — returned once, never again>",
"addresses": {
"BITCOIN": "bc1q5psk2q8t4x495ap75zghxmyvlmk59lr2u93r6j",
"ETHEREUM": "0xc0c548339ee2af89c078200cabd1b7c7b47d911a",
"BSC": "0xc0c548339ee2af89c078200cabd1b7c7b47d911a",
"TRON": "THxvJmtZghePy5XqDE7HKawvFAsAgxcWZt",
"POLYGON": "0xc0c548339ee2af89c078200cabd1b7c7b47d911a",
"SOLANA": "EB81pobCpSX16xKeuwYyNMDuZEcpWuHcmimQ9LbdT63d"
}
},
"requestId": "b7a1f0c2-3d4e-4a5b-9c6d-7e8f90a1b2c3"
}

Save the mnemonic now — this is your only chance. It is returned exactly once, never stored in plaintext, and cannot be retrieved later — there is no export endpoint. Save the password too: it decrypts the keys, it is required on every transfer, and if it is lost the funds cannot be moved by anyone.

The password must be at least 8 characters. The three EVM chains deliberately share one address — it is one account used on Ethereum, BSC and Polygon.

Importing an existing seed

Pass existingSeed instead of letting the service generate one, and /create derives the wallets from your mnemonic. If those wallets already exist, it adopts the existing user rather than failing — and in that case the response contains no mnemonic field at all (you already have it).

mnemonic is therefore conditional. Code like result.mnemonic.split(' ') will crash on an adopted user. Always null-check it.

4. Check a balance

assetId picks the chain and the asset. 21 is USDT on Ethereum — see Asset IDs.

curl -X POST "$BASE_URL/balance" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "userId": "66b1f0c23d4e4a5b9c6d7e8f", "assetId": 21 }'
{ "result": "250.5", "requestId": "b7a1f0c2-3d4e-4a5b-9c6d-7e8f90a1b2c3" }

The balance is exact, down to the last base unit — nothing is floored to "0", so "0" means the address holds nothing of that asset.

5. Estimate the fee

/fee is the one endpoint that takes raw addresses instead of a userId — it never touches a wallet.

curl -X POST "$BASE_URL/fee" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"assetId": 21,
"sender": "0xc0c548339ee2af89c078200cabd1b7c7b47d911a",
"recipient": "0x1cc5ca781f2f9ee13eb89283f71abaa24bb61190"
}'
{ "result": "0.000315", "requestId": "b7a1f0c2-3d4e-4a5b-9c6d-7e8f90a1b2c3" }

The fee is denominated in the chain’s native asset, not the asset you are sending. Sending USDT on Ethereum costs ETH — so a token wallet still needs a native balance for gas.

On a native send, this fee comes out of the amount. Send "0.5" ETH and the recipient receives 0.5 ETH − fee; exactly 0.5 ETH leaves the wallet. If the recipient must receive an exact figure, add the fee quoted here to the amount you send.

Token transfers behave the other way round: the recipient gets the full amount, and the fee is charged separately in the native coin.

Tron is the exception. It does not return a currency amount, but the resources the transfer will consume: { "bandwidth": 345, "energy": 130285 }. Whatever your account does not already cover with free or staked resources is burned as TRX.

6. Send

/send is the one endpoint that requires you to supply X-Request-Id. It is the idempotency key for the transfer: generate it once, before the first attempt, and send the same value on every retry.

curl -X POST "$BASE_URL/send" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Request-Id: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"userId": "66b1f0c23d4e4a5b9c6d7e8f",
"assetId": 21,
"recipient": "0x1cc5ca781f2f9ee13eb89283f71abaa24bb61190",
"amount": "25.5",
"password": "a-strong-password"
}'
{
"result": { "txHash": "0x2d3e4f5061728394a5b6c7d8e9f0a1b22d3e4f5061728394a5b6c7d8e9f0a1b2" },
"requestId": "b7a1f0c2-3d4e-4a5b-9c6d-7e8f90a1b2c3"
}

Generate the id once and reuse it to retry — do not call uuidgen again. Repeating a transfer with the same id is safe: a completed one replays its original txHash instead of broadcasting again. Repeating it with a new id is a second transfer, and the funds go out twice. See Reliability.

A txHash is not a confirmation. It means the network accepted the transaction, not that it succeeded. Poll /txid until status is success or failed.

7. Confirm it landed

curl -X POST "$BASE_URL/txid" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userId": "66b1f0c23d4e4a5b9c6d7e8f",
"assetId": 21,
"txHash": "0x2d3e4f5061728394a5b6c7d8e9f0a1b22d3e4f5061728394a5b6c7d8e9f0a1b2"
}'
{
"result": {
"hash": "0x2d3e4f5061728394a5b6c7d8e9f0a1b22d3e4f5061728394a5b6c7d8e9f0a1b2",
"type": "send",
"amount": "25.5",
"fee": "0.000315",
"status": "success",
"date": "2026-07-11T09:14:22.000Z"
},
"requestId": "b7a1f0c2-3d4e-4a5b-9c6d-7e8f90a1b2c3"
}

While the transaction is unconfirmed, status is pending and date is null. Every chain is normalised to this same row shape, so you write the polling loop once and it works everywhere.

Check for what you need to know. Poll /txid until a send’s status settles, and poll /balance or /transactions to see funds a user has received. Read Reliability before you design around it.

Where to go next

  • Authentication — the token lifecycle, and API credentials vs the wallet password.
  • Asset IDs — the integer for every chain and token, plus send minimums.
  • Reliability — how to poll, how to keep your own history, and how to retry a /send without sending twice.
  • Errors — the error envelope and what each status means.
  • API Reference — every endpoint, with a request and response example per asset.