Docs menuAuthentication

API reference

Authentication

Reads are public. Anything for one wallet needs a wallet sign in.

Who needs it

RequestNeeds
Public reads (coins, trades, prices, leagues...)Nothing
Reads for one wallet (your coins, tickets, settings...)A session cookie
Changes (chat, favorites, settings, launches, claims)A session cookie and the CSRF header
Trade builds, referral visits, the Solana relayThe CSRF header only

Sign in, step by step

  1. 1Ask for a challenge with the wallet address. You get a message and a token.
  2. 2Sign the message bytes with the wallet (an ed25519 signMessage). Signing moves nothing.
  3. 3Send the wallet, the base58 signature and the token back. The answer sets the session cookie.
  4. 4Send the cookie with every request that needs a session.
  • A challenge works once and expires after 5 minutes. The server checks its own copy of the message, so the browser cannot change what was signed.
  • A session lasts 7 days.

Ask for a challenge

POST/api/authNo sign in, CSRF header

Starts a sign in. Answers plain JSON (no envelope).

Sign in
Not needed
CSRF header
Needed: x-zpad: zpad
Cached
Never
Rate limit
20 an hour per address (IP)

Body

NameTypeWhat it is
step"challenge"What to do.
walletstringThe Solana address that signs in.

Response

200
{
  "message": "<the message to sign>",
  "token": "<challenge token>"
}

Errors

StatusMessageWhen
400That is not a Solana address.wallet is not a Solana address.
403Missing request header.No CSRF header.
403Cross origin request refused.The request came from another site.
429Too many sign in attempts. Wait a while and try again.More than 20 challenges in an hour.
503Signing in is not available just now. Try again in a moment.A server problem.

Example

Terminal
curl -s -X POST "/api/auth" \
  -H "x-zpad: zpad" \
  -H "content-type: application/json" \
  -d '{"step":"challenge","wallet":"<wallet address>"}'

Verify the signature

POST/api/authNo sign in, CSRF header

Finishes a sign in and sets the session cookie. Answers plain JSON.

Sign in
Not needed
CSRF header
Needed: x-zpad: zpad
Cached
Never
Rate limit
None of its own

Body

NameTypeWhat it is
step"verify"What to do.
walletstringThe same address as in the challenge.
signaturestringThe wallet's ed25519 signature of the message bytes, base58.
tokenstringThe token from the challenge.

Response

200
{
  "wallet": "<wallet address>"
}

Errors

StatusMessageWhen
400The sign in request expired. Try again.The token is unknown, used or older than 5 minutes, or belongs to another wallet.
400The signature could not be read.The signature is not base58.
401The signature does not match this wallet.The signature is not this wallet's signature of the message.
503Signing in is not available just now. Try again in a moment.A server problem.

Example

Terminal
curl -s -X POST -c cookies.txt "/api/auth" \
  -H "x-zpad: zpad" \
  -H "content-type: application/json" \
  -d '{"step":"verify","wallet":"<wallet address>","signature":"<base58 signature>","token":"<challenge token>"}'

In the browser

The same steps in a page, with the wallet's own signMessage. bs58 is the base58 package.

JavaScript
const headers = { "content-type": "application/json", "x-zpad": "zpad" };
const post = (body) => fetch("/api/auth", { method: "POST", headers, body: JSON.stringify(body) }).then((r) => r.json());

const { message, token } = await post({ step: "challenge", wallet });
const signed = await signMessage(new TextEncoder().encode(message)); // your wallet's signMessage
const { wallet: signedIn } = await post({ step: "verify", wallet, token, signature: bs58.encode(signed) });

Sign out

POST/api/authNo sign in, CSRF header

Ends the session and clears the cookie.

Sign in
Not needed
CSRF header
Needed: x-zpad: zpad
Cached
Never
Rate limit
None of its own

Body

NameTypeWhat it is
step"logout"What to do.

Response

200
{
  "ok": true
}

Example

Terminal
curl -s -X POST -b cookies.txt "/api/auth" \
  -H "x-zpad: zpad" \
  -H "content-type: application/json" \
  -d '{"step":"logout"}'

Who is signed in

GET/api/auth/meNo sign in

The wallet of the current session, or null. GET /api/auth answers the same.

Sign in
Not needed
CSRF header
Not needed
Cached
Never
Rate limit
None of its own

Response

200
{
  "wallet": "<wallet address>" | null
}

Example

Terminal
curl -s -b cookies.txt "/api/auth/me"
Name__Host-gz_sid (gz_sid on a local http address)
Lasts7 days
FlagsHttpOnly, Secure, SameSite Lax, Path /
StoredThe server keeps only a SHA-256 hash of it

With curl, save the cookie with -c cookies.txt when you verify, and send it back with -b cookies.txt, as the examples in these docs do.

The CSRF header

Every POST and PATCH needs the header x-zpad: zpad. Browsers cannot send it from another site without asking first, which the API never allows. A request whose Origin is another site is refused with 403.

Requests without an Origin header, like curl, pass on the header alone.

Cookies On Our Website. We use cookies to keep you signed in and remember your settings.