@moovehandle or wallet address, across any supported chain.
Base URL
https://api.moove.xyz. Generated against openapi.json.moove.xyz never signs
The four steps
1
Quote the payout
POST /v1/quote/send returns a quote whose steps[].transactionData is unsigned. With a key, this is also where a funding wallet you have not connected by signature is refused — before you have signed anything.2
Sign and broadcast — locally
With your own key, using your own library. moove.xyz is absent from this step entirely. Go through
steps in order, skip any whose chainType is not your wallet’s, and keep the last hash.3
Record it
POST /v1/transaction with the broadcast hash and the quote you signed. Returns a Moove id. Safe to retry with the same body if the response is lost.4
Poll to settlement
GET /v1/transaction/{id} until status is settled.Before you start
You need four things. Three take a minute; the fourth is the one people miss.1
An API key scoped to the Moove Send Agent
Create it at moove.xyz/dashboard/api-keys and pick Moove Send Agent in the permissions picker. That grants
send:quote, send:create and transaction:read — all three, because a payout needs to quote, record and poll.The plaintext key is shown once. Copy it straight into your secret manager.Check the key is set before your first call. See the warning below.Moove API Keys →2
The base URL
Production is
https://api.moove.xyz. It is shown with the key when you create it, and it is not a secret — but do not guess it, because a key is only valid against the deployment it was minted in.3
A funding wallet connected BY SIGNATURE
This is the one that catches people. See the warning below — read it before you write any code.Its address cannot be discovered through the API — no endpoint a key can call lists your wallets. Read it once from Settings → Account → Wallet on the web, or from Wallet in your profile in the Moove App, and hold it in your own config.
4
A local signer
moove.xyz ships none, and this API cannot sign. You need a wallet library in your own project —
viem, ethers, @solana/web3.js, whatever you already use — holding a key moove.xyz never sees.Finding a chain id and a token address
POST /v1/quote/send takes numeric chain ids and token contract addresses, not names. Two endpoints give you both. Neither is listed in openapi.json — they are large catalogues rather than integration endpoints — but both are served, and both work without a credential:
GET /v1/chain lists every supported chain with its numeric id. GET /v1/token lists tokens with their contract addresses and decimals — scoped to one chain with chain_id (not chainId), or searched across every chain by symbol or name with q.
EVM chains use their standard chain ids (1 for Ethereum, 8453 for Base, and so on). The three non-EVM chains do not — this API gives them its own numeric ids, and guessing a name like "solana" will not work:
Supported Blockchains → · Supported Tokens →
Before your first payout
Moove Wallet →Quote a send
endpoint
required
Requires the
send:quote scope.The
steps[].transactionData in the response is unsigned. You sign and broadcast it yourself. This endpoint moves nothing.With an API key,
fromAddress and toAddress are both required, and fromAddress must be a wallet connected to your Moove account by signature. Each rule is checked here, before you sign anything, so a refusal costs you nothing:Both fields are optional only for anonymous pricing, which is how the Moove Widget shows an indicative price before a wallet is connected. That price is never meant to be signed.
Each address must be of its own leg’s chain family — with or without a key. The families are EVM, SVM (Solana), TVM (Tron) and BVM (Bitcoin), read from the chain catalogue.
These are the same codes a missing address gets. The
message tells them apart by naming both families, for example toAddress is on the EVM chain family, but chain 792703809 is SVM. A cross-family send is fine as long as each address matches its own chain: an EVM fromAddress on chain 1 paying a Solana toAddress on chain 792703809 is quoted normally.Body
string
required
Amount to send, denominated in the source token. Must be greater than 0.Send it as a string to avoid floating-point rounding —
"10.5", never 10.5. A JSON float cannot represent every token amount exactly, and here the rounding error is a real mispaid invoice.Bounded to the amount column’s precision: at most 78 digits with at most 18 decimal places. "0", a negative value, "NaN" and "Infinity" are all rejected with a 422.string
required
The wallet funding the send. Required with an API key.It must be a wallet connected to your Moove account by signature. A receive-only address you added by pasting it cannot send, and the quote refuses it with
409 SEND_ACCOUNT_NOT_READY before you sign.Omitting it returns 400 INVALID_SOURCE. Without that refusal, the quote would be built against a placeholder source rather than your wallet, and the transaction you signed would not be yours to record. A value that is not a valid address on any supported chain returns 422 INVALID_DESTINATION — the address validator shares the destination code.A valid address of the wrong chain family for fromChainId — an EVM address with a Solana chain id — returns 400 INVALID_SOURCE, with or without a key.string
required
The recipient. Required with an API key. Accepts two forms:
A handle is lowercase letters, digits and underscores, 3 to 63 characters.
"@Alice" and "@ab" both fail. There is no alias resolution on this API — a dotted name like "alice.moove.xyz" is not a handle. Anything that is neither a valid handle nor a valid address is rejected with 422 INVALID_DESTINATION. Resolve any alias of your own to a handle or an address before calling.With a key, omitting it — or naming a wallet moove.xyz operates itself, which no payout may be sent to — returns 400 INVALID_DESTINATION.An address of the wrong chain family for toChainId returns 400 INVALID_DESTINATION, with or without a key. A handle is checked through the wallet it pays: its owner’s default wallet. So "@alice" with a Solana toChainId, when Alice’s default wallet is on an EVM chain, returns 400 INVALID_DESTINATION — quote a toChainId of that wallet’s family.A well-formed handle that belongs to nobody fails the quote with 500 CANNOT_CREATE_QUOTE. Check the handle rather than retrying.string
required
Chain id the funds leave from, e.g.
"1" for Ethereum mainnet.string
required
Chain id the funds arrive on. May differ from
fromChainId — a cross-chain send is quoted and routed for you, including one to a different chain family, as long as toAddress is of this chain’s family.string
required
Contract address of the token being sent, on
fromChainId.string
required
Contract address of the token the recipient receives, on
toChainId. May differ from fromTokenAddress.Request
Response
TransactionStepData[]
required
The transactions to execute, in order. A cross-chain or cross-token payout can return more than one — often an approval, then the transfer.Sign and broadcast them in order, waiting for each to confirm, and skip any step whose
chainType is not your wallet’s. Record the last step’s hash: settlement is tracked by the hash you record, so recording an approval leaves the transaction processing for good. It does not error. How to sign each chainType →string
required
The amount leaving the source wallet.
string
required
The amount the recipient receives, after routing and fees.
TokenData
required
The source token, including its chain.
TokenData
required
The destination token, including its chain.
number
required
Expected seconds to settlement. Use it to size your first poll delay.
string
required
Estimated network fee on the source chain, in
sourceBlockchainToken.string | null
The protocol cost for this route, in
protocolCostToken.TokenData | null
The token the protocol cost is charged in.
TokenData
required
The token the source-chain network fee is paid in.
string
required
The funding wallet, as you sent it.
string
required
The recipient’s wallet address. A handle you sent is resolved to its owner’s wallet here.
number
required
toAmount per unit of fromAmount.string
required
USD value of
fromAmount. toAmountUsd, sourceBlockchainFeeAmountUsd and protocolCostAmountUsd are the same for their amounts. Indicative only — 0 when a token has no price.string | null
The route provider that priced the quote.
serviceFeeAmount, serviceFeeAmountUsd and serviceFeeToken are deprecated and always null — the fee is protocolCost*. fiatAmount, fiatCurrency and quoteToken belong to fiat payment links and are null on a send quote.Record a broadcast transaction
endpoint
required
Requires the
send:create scope."product": "swap" and the swap:create scope — see Moove Swap.
Records a transaction you have already signed and broadcast yourself, and starts tracking it to settlement.
This endpoint sends nothing and signs nothing. By the time you call it, the funds have already moved. Its job is to attribute the on-chain transaction to your Moove account and begin polling it.
Body
string
required
The on-chain hash you just broadcast — the last step’s, when the quote had more than one.
QuoteData
required
The quote object from
POST /v1/quote/send — the same one you built the signed transaction from.string
required
"send".Required when authenticating with a key, because the scope the request needs is derived from it. A missing or unsupported value is rejected with 422 INVALID_TRANSACTION_PRODUCT rather than defaulted — defaulting it would pick a permission on your behalf.string
Your own reference — payout id, invoice number. Maximum 500 characters.Set it. It is how a human reconciles the row in the Moove dashboard back to what it paid for, and it is returned to you on the polling read.
Request
Response
string
required
The Moove transaction id. Poll this.
string
required
processing, settled or failed. A freshly recorded send is processing.string
required
The on-chain hash you supplied.
string
required
The funding wallet.
string
required
The recipient’s resolved wallet address.
string
required
Amount sent, in the source token.
string
required
Amount received, in the destination token.
string | null
Your reference, as supplied.
string
required
send, receive or swap, read from your side of the row. A payout you recorded is send.string
required
When the transaction was recorded, ISO 8601 in UTC.
TokenData
required
The token sent, including its chain.
destinationToken is the token received.string | null
The hash on the destination chain, for a cross-chain send once it is known.
string | null
The source hash on the chain’s block explorer.
url is the transaction’s moove.xyz page.Recipient identity fields are
null for a key. destinationUserId, destinationUserHandle and destinationProfileImage come back empty — and for a call made without a key, both sides’ identity does.You supplied the destination, so you already know who you paid. Returning the recipient’s Moove profile would turn this endpoint into an address-to-identity lookup for anybody willing to send dust.Poll a transaction
endpoint
Accepts a key carrying
transaction:read, send:create or swap:create, a session, or no credential at all. A key holding none of those — a Receive-only key — gets 403.This endpoint is served but is not listed in
openapi.json. Its absence from the schema is not a removal — it returns full user objects on both sides, a larger payload than an integration needs, so it is not advertised on the documented surface. It is the endpoint to poll.Request
cURL
Calling this with your key is what returns your own
description. An anonymous read of the same id — the public /tx/<id> share link — has identity fields and the memo redacted, because the reader is not a party to the transaction.Recipient identity stays redacted for key holders either way, exactly as on the record call above.List transactions
endpoint
required
Requires the
transaction:read scope.A key sees every transaction its owner can see — including sends made from the Moove App and by a sibling key. This is the endpoint to reconcile a batch against, rather than polling ids one by one.
Query parameters
string
Filter by status. One of
processing, settled, failed. Omit for all.integer
default:"0"
Page offset. Follow
nextOffset from the previous response.Request
cURL
Response
A page of the same objectsPOST /v1/transaction returns, with limit, offset and nextOffset.
Pagination →
Rate limits
Concrete numbers, so you pace a payout loop rather than meeting the cap as a429:
Each limit applies per API key and per source IP, whichever is reached first. Ten payouts a minute is 600 an hour. There is no batch endpoint — a batch is your own loop over these calls, so sleep between iterations.
Rate Limits →
Errors
Branch oncode, never on message.
Errors →
Not in the API
moove.xyz does not sign, and ships no signer. There is no SDK, no key storage and no example that writes a key to disk. Signing is done by your own code, with your own library.Webhooks cover payment links only — there is no webhook for a payout yet. Poll
GET /v1/transaction/{id}, or reconcile with GET /v1/transaction.Next
Moove Swap
Swapping and bridging your own funds.
Authentication
Keys, scopes and the header.
Moove Agentic Payments
Build the integration with a prompt.
Moove Send
The product, in the Moove App.