Auth
API Key Login
Authentication
Provision an API key via the GRVT UI, then exchange it for a session cookie.
# These are the variables you will need to set manually
GRVT_API_KEY="<insert_key_here>"
GRVT_SUB_ACCOUNT_ID="<insert_sub_account_id_here>"
Then, choose the environment you want to authenticate against.
# stg
GRVT_AUTH_ENDPOINT="https://edge.staging.gravitymarkets.io/auth/api_key/login"
# testnet
GRVT_AUTH_ENDPOINT="https://edge.testnet.grvt.io/auth/api_key/login"
# prod
GRVT_AUTH_ENDPOINT="https://edge.grvt.io/auth/api_key/login"
Now, let's create a cookie that will be used to authenticate with our API Services.
echo $GRVT_API_KEY
echo $GRVT_SUB_ACCOUNT_ID
echo $GRVT_AUTH_ENDPOINT
RESPONSE=$(
curl $GRVT_AUTH_ENDPOINT \
-H 'Content-Type: application/json' \
-H 'Cookie: rm=true;' \
-d "{ \"api_key\": \"$GRVT_API_KEY\" }" \
-s -i
);
GRVT_COOKIE=$(echo "$RESPONSE" | grep -i 'set-cookie:' | grep -o 'gravity=[^;]*')
echo $GRVT_COOKIE
echo "$RESPONSE" | tail -1
Response
On success (200 OK), a session cookie (gravity=...) is set and the response body contains:
{
"status": "success",
"location": "",
"funding_account_address": "0xYourFundingAccountAddress",
"sub_account_id": "123456789"
}
| Field | Type | Required | Description |
|---|---|---|---|
status |
string | Yes | Login result status (e.g. "success") |
location |
string | Yes | |
funding_account_address |
string | Yes | The funding account address associated with the account |
sub_account_id |
string | No | The sub-account ID, present only when the API key was generated from a Trading Account |
Wallet Login
Alternatively, you can authenticate directly with your EVM signing wallet using an EIP-712 typed-data signature — no API key required.
Endpoint: POST /auth/wallet/login
Signing Payload
Construct and sign the following EIP-712 typed data:
Domain
| Field | Value |
|---|---|
name |
GRVT Exchange |
version |
0 |
chainId |
GRVT chain ID — see Chain IDs |
Struct
WalletLogin(address signer, uint32 nonce, int64 expiration)
| Field | Type | Description |
|---|---|---|
signer |
address |
Your EVM wallet address (the main signing wallet registered on GRVT) |
nonce |
uint32 |
A random number chosen by the client. Each (address, nonce) pair can only be used once within its validity window. |
expiration |
int64 |
Unix timestamp in nanoseconds. Must be in the future. The server enforces a maximum of 5 minutes from the time of the request — signatures with a longer window are rejected with 400. Use the Server Time endpoint to get the current server time. |
Sign the struct with eth_signTypedData_v4 to produce V, R, S.
Request
The signature field uses the common Signature DTO shared across all signed endpoints.
{
"address": "0xYourWalletAddress",
"signature": {
"signer": "0xYourWalletAddress",
"v": 27,
"r": "0x<32-byte hex>",
"s": "0x<32-byte hex>",
"nonce": 305419896,
"expiration": "1735689600000000000",
"chain_id": "325"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
address |
string | Yes | Wallet address with 0x prefix |
signature.signer |
string | Yes | The address (public key) of the wallet signing the payload |
signature.v |
integer | Yes | Recovery ID — must be 27 or 28 |
signature.r |
string | Yes | Hex-encoded R component |
signature.s |
string | Yes | Hex-encoded S component |
signature.nonce |
uint32 | Yes | Random nonce used when constructing the signed payload |
signature.expiration |
string | Yes | Expiration in nanoseconds as a string (must match the signed value) |
signature.chain_id |
string | Yes | GRVT L2 chain ID as a string — see Chain IDs. Zero falls back to GRVT default. |
Response
On success (200 OK), a session cookie (gravity=...) is set and the response body contains:
{
"status": "success",
"location": "",
"funding_account_address": "0xYourFundingAccountAddress",
"sub_account_id": "123456789"
}
| Field | Type | Required | Description |
|---|---|---|---|
status |
string | Yes | Login result status (e.g. "success") |
location |
string | Yes | |
funding_account_address |
string | Yes | The funding account address associated with the account |
sub_account_id |
string | No | The sub-account ID, present only when the API key was generated from a Trading Account |
Use the session cookie to authenticate subsequent API calls in the same way as API key login.
Example (ethers.js)
import { ethers } from "ethers";
// Set GRVT_AUTH_ENDPOINT to your environment (see API Key Login above)
const GRVT_AUTH_ENDPOINT = "<insert_auth_endpoint_here>";
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const GRVT_CHAIN_ID = 325;
const domain = {
name: "GRVT Exchange",
version: "0",
chainId: GRVT_CHAIN_ID,
};
const types = {
WalletLogin: [
{ name: "signer", type: "address" },
{ name: "nonce", type: "uint32" },
{ name: "expiration", type: "int64" },
],
};
// Fetch server time and compute expiration (max 5 minutes, server-enforced)
const { server_time } = await fetch("https://market-data.grvt.io/time").then(r => r.json());
const expiration = BigInt(server_time) * BigInt(1_000_000) // ms → ns
+ BigInt(5 * 60) * BigInt(1_000_000_000); // + 5 minutes in ns
const nonce = Math.floor(Math.random() * 0xFFFFFFFF);
const address = await signer.getAddress();
const rawSig = await signer.signTypedData(domain, types, { signer: address, nonce, expiration });
const sig = ethers.Signature.from(rawSig);
const response = await fetch(`${GRVT_AUTH_ENDPOINT}/wallet/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
address,
signature: {
signer: address,
v: sig.v,
r: sig.r,
s: sig.s,
nonce,
expiration: expiration.toString(),
chain_id: GRVT_CHAIN_ID.toString(),
},
}),
credentials: "include",
});
const GRVT_COOKIE = response.headers.get("set-cookie")?.match(/gravity=[^;]*/)?.[0];
const { status, funding_account_address, sub_account_id } = await response.json();