REST APIHow-tos
Log in
Get a JWT for a wallet with two requests, and send it with the wallet's own requests.
Endpoints that act for a wallet, such as creating, reading and removing its orders, need a JWT for that wallet. You get one by signing a message with the wallet's key. This page shows the requests for a regular wallet (an EOA). For a Predict account, the message is signed through the SDK instead: see Authentication.
1. Get the message
GET /v1/auth/message returns the message to sign. It needs only your API key.
{
"success": true,
"data": {
"message": "Please sign this message to log in. By signing, you accept the Predict Terms of Service, which you can find here: https://docs.predict.fun/terms-of-service Timestamp: 1793832395205"
}
}The message ends with the time it was issued. Sign and send it within 10 minutes.
2. Sign it
Sign the message exactly as you received it, as a personal message (EIP-191). This is the standard personal_sign that every Ethereum library and wallet supports.
3. Get the JWT
Send the wallet's address, the message and the signature to POST /v1/auth:
{
"signer": "0xYourWalletAddress",
"message": "Please sign this message to log in. …",
"signature": "0x…"
}The response holds the JWT in data.token:
{
"success": true,
"data": { "token": "eyJ0eXAiOiJKV1Qi…" }
}4. Use it
Send the JWT in the Authorization header, as Bearer <jwt>, next to your API key. A JWT is valid for 24 hours. After that, log in again.
The whole flow
This logs in, then lists the wallet's open orders with the JWT:
import { Wallet } from "ethers";
const apiKey = "YOUR_API_KEY";
const apiUrl = "https://api.predict.fun";
const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!);
// 1. Get the message to sign.
const messageResponse = await fetch(`${apiUrl}/v1/auth/message`, {
headers: { "x-api-key": apiKey },
});
const { message } = (await messageResponse.json()).data;
// 2. Sign it as a personal message (EIP-191).
const signature = await wallet.signMessage(message);
// 3. Exchange the signature for a JWT.
const loginResponse = await fetch(`${apiUrl}/v1/auth`, {
method: "POST",
headers: { "Content-Type": "application/json", "x-api-key": apiKey },
body: JSON.stringify({ signer: wallet.address, message, signature }),
});
const login = await loginResponse.json();
if (!loginResponse.ok) throw new Error(`${login.error}: ${login.message}`);
const jwt = login.data.token;
// 4. Send the JWT with the API key on the wallet's own requests.
const ordersResponse = await fetch(`${apiUrl}/v1/orders?status=OPEN`, {
headers: { "x-api-key": apiKey, Authorization: `Bearer ${jwt}` },
});
const orders = await ordersResponse.json();
console.log(`${orders.data.length} open orders`);When it fails
A rejected login is a 401 with error: "unauthorized". The message says why:
message | Cause | Fix |
|---|---|---|
invalid signature | The signature doesn't match the message and signer. | Sign the message exactly as you received it, with the key of signer. |
invalid message | The message isn't the one the API sent. | Send the message back unchanged. |
invalid message timestamp | The message was issued more than 10 minutes ago, or its timestamp was changed. | Get a new message and sign it right away. |
authorization error | The API key is missing or invalid, or a later request has no JWT or an expired one. | Check the API key. Log in again, and send the JWT as Bearer <jwt>. |
A body that can't be read, such as one with a malformed signer address, gets a 400 instead.