Skip to content

API conventions

Read this before the endpoint pages. The API predates any style guide, so the conventions are descriptive rather than prescriptive — but knowing them saves a lot of guessing.

Base URLs

EnvironmentBase
Productionhttps://api.blazpay.com/api
Localhttp://localhost:5000/api

The frontend resolves it in defi-dex/src/utils/axios.ts:

ts
baseURL = VITE_API_URL || (isLocalhost ? 'http://localhost:5000/api'
                                       : 'https://api.blazpay.com/api')

swap-sdk resolves its own (swap-sdk/src/utils/constants.ts), defaulting to https://api.blazpay.com/api/defi and only using localhost when the browser host matches localhost, 127. or 192.168.. In Node it always uses production. Override with configure({ baseApiUrl }).

Response envelope

Most /api endpoints use sendResponse from utils/helpers.js:

json
{
  "status": 200,
  "message": "success",
  "data": { },
  "errors": null,
  "notShowPopUp": true
}
FieldNotes
statusDuplicates the HTTP status
messageDefaults to "success"
dataThe payload, null on error
errorsnull on success; an object or string otherwise
notShowPopUpA UI hint, not an API concept — tells the frontend whether to surface a toast. Defaults to true

The envelope is not universal

Several controllers — particularly the provider proxies and the newer /api/v1 surface — return the upstream payload directly, or a bare object. Do not write a client that assumes .data unwrapping everywhere. Check the endpoint page.

There is also sendResponseAi(res, statusCode, message, data, save, errors) for the legacy AI chat path, with a save flag instead of notShowPopUp.

Authentication

Four different patterns coexist. Which one applies depends on the route, not on a header convention.

PatternWhereMechanism
None — wallet in the body/params is trustedMost /defi/*, all BlazAIThe wallet value is taken at face value
JWT bearer/user, /admin, rewards, gamesmiddleware/authMiddleware.js, JWT_SECRET / JWT_ADMIN_SECRET
Wallet signatureMutating Autopilot routesmiddleware/walletAuth.js — see below
Socket authSocket.io namespacesmiddleware/authSocketMiddleware.js

The wallet-signature pattern

body       = { ...payload, wallet, timestamp, signature }
signedBody = body minus `signature`
digest     = sha256(utf8(JSON.stringify(sortDeep(signedBody))))   // keys sorted recursively
message    = `Blazpay <action> ${digest} ${timestamp}`
signature  = personal_sign(message)                              // EIP-191
PropertyValue
Window±5 minutes
Replay guardRedis, TTL 600 s
ScopeThe action string is inside the message, so a signed pause cannot replay as create
Resultreq.authWallet, lowercased and verified

Handlers must read req.authWallet, never req.body.wallet.

Why most routes are unauthenticated

For DCA, funds only move via user-signed transactions — the backend merely records status, so a forged request cannot steal anything. For Autopilot, a forged "raise my spend cap" would change what the executor does with escrowed funds, so it needs the signature. The asymmetry is deliberate, not an oversight.

That said: audit any new route that touches escrowed value. See security model.

Server-Sent Events

Two SSE endpoints, with different framing.

Quotes — GET /defi/quotes

data: {"id":"…","aggregator":"lifi","amount":"…", …}\n\n
data: {"id":"…","aggregator":"odos", …}\n\n
event: end\n\n

Each data: line is one provider's meta object. Clients append and re-sort; the best route changes as quotes arrive. An end event closes the stream.

Consume with EventSource, not fetch — the frontend uses new EventSource(baseURL + '/defi/quotes?...').

Chat — POST /v1/blaz/message/stream

Three named event types:

EventPayloadOrder
meta{ action, data }First, so the card can render before prose
tokenA text chunkRepeated
doneLast

Client: defi-dex/src/apis/ai.api.ts → sendAIMessageStream({ onToken, onMeta, onDone }).

Addresses

RuleDetail
CasingAccepted in any casing. Lowercased on write in Autopilot policies and wallet auth, because hashes depend on it
Native assetThree sentinels mean native: 0x0000…0000, 0xeeee…eeee, and Polygon's 0x…1010
TRONBase58 (T…) externally, '41' + hex internally. Convert with tronweb.address.fromHex
ComparisonAlways compare lowercased. Token and Network lookups do

Chain identifiers

chainId in a request means Network.id, which is the EVM chain id for EVM chains and a synthetic value otherwise:

ChainchainId
Any EVM chainIts real chain id
Solana102
TRON728126428

102 is not a real chain id

Anything treating chainId as EVM-shaped — a relayer lookup, an explorer URL builder — must special-case the non-EVM two.

Amounts

ContextRepresentation
Quote requestsHuman units (1.5 for 1.5 ETH)
Quote meta.amountBase units as a string
Autopilot policy amountA positive numeric string/^\d+(\.\d+)?$/ — never a JS number
Contract callsBase units, BigNumber/bigint

Why policy amounts are strings

Float precision drift between the frontend's hash input and the backend's would change the keccak256, so the policy signature would not verify. Strings avoid it entirely.

Idempotency and correlation

There is no Idempotency-Key header. Correlation happens two ways:

  • Quote id — a UUID from meta.id, valid for 5 minutes in Redis. It correlates a quote to its build.
  • Transaction UUID — trade records are created with a UUID as txHash, then patched with the real hash. ?hash=<uuid> in the browser URL recovers an in-flight trade.

Pagination

Inconsistent. Some endpoints take page/limit, some take offset, some return everything. The approval scanner caps at 25 by allowance size; the token picker slices the top 200 by order. Check the endpoint.

Caching

CacheKeyTTL
Quotesmeta.id in Redis5 min
Fee summaryfees:summary:v15 min, bypass with ?force=1
Token safety scans(address, chainId) in Mongo6 h
Wallet cardsMongoUntil POST /generate
Provider cataloguesMongoUntil the save-* endpoint is called
Autopilot pricesRedis rolling seriesRolling window

HTTP semantics

Loose. GET is used for some administrative mutations (GET /defi/alchemy/save-crypto refreshes a cache), POST for some reads (POST /defi/1inch is a proxied query). PATCH appears on transaction updates. Do not infer safety from the verb.

Body limit

express.json({ limit: '20mb' }) — large enough for pasted Solidity and generated dApp HTML.

CORS

An explicit allowlist in bz-backend/index.js with credentials: true. Socket.io CORS is configured separately in services/socket.js. Full list: domains.

Rate limiting

express-rate-limit is a dependency. Verify where it is actually applied before assuming a route is protected — most are not.

Known route bugs

BugDetail
router.get('relayer/tx/data', …)Missing leading slash in routes/defi.routes.js, making the intended /api/defi/relayer/tx/data unreachable
/api/supportForDasboardTypo in a live path. Fixing it is a breaking change
/api/clippers and /api/v1/clippersThe same route file mounted twice
/api/nftMounted twice — nftRoutes then nftMetadataRoutes

Internal + developer documentation. Usage figures are point-in-time snapshots — re-verify before publishing them.