Skip to main content

Overview

All programmatic access to the Limitless Exchange API requires authentication via scoped API tokens with HMAC-SHA256 request signing.
API keys are deprecated and no longer available for new users. Existing API key users should migrate to scoped API tokens. See Legacy API Keys below.

Scoped API Tokens (HMAC)

Scoped API tokens are the authentication method for the Limitless Exchange API. Every request is authenticated by signing it with HMAC-SHA256 using your token’s secret. Tokens are scoped to control what actions they can perform.

Scopes

ScopeDescription
tradingPlace and cancel orders. Required base scope for delegated_signing.
account_creationCreate sub-account profiles under your partner account.
delegated_signingServer signs orders on behalf of sub-accounts (requires trading scope). Enables Web2 partners to submit orders without end users managing private keys.
withdrawalWithdraw ERC20 balances from managed server-wallet sub-accounts to partner-owned addresses.

HMAC Request Signing

Required headers:
HeaderDescription
lmts-api-keyYour token ID (received at token creation)
lmts-timestampISO-8601 timestamp (must be within 30 seconds of server time)
lmts-signatureBase64-encoded HMAC-SHA256 signature
SDK users should pass HMAC credentials to the SDK client (hmacCredentials / hmac_credentials / WithHMACCredentials) and let the SDK build these headers automatically. Manual header signing is only needed when you call the REST API directly.
Canonical message format:
{ISO-8601 timestamp}\n{HTTP METHOD}\n{request path with query string}\n{request body}
The path component must include the full request URL path and query string (e.g., /orders/all/btc-100k?onBehalfOf=42), not just the pathname. For requests without a query string, use the plain path (e.g., /orders). For GET requests, the body component is an empty string.
Example (TypeScript):
import { createHmac } from 'crypto';

function signRequest(
  tokenId: string,
  secret: string,     // base64-encoded, received at token creation
  method: string,
  path: string,
  body: string = '',
): Record<string, string> {
  const timestamp = new Date().toISOString();
  const message = `${timestamp}\n${method}\n${path}\n${body}`;

  const signature = createHmac('sha256', Buffer.from(secret, 'base64'))
    .update(message)
    .digest('base64');

  return {
    'lmts-api-key': tokenId,
    'lmts-timestamp': timestamp,
    'lmts-signature': signature,
  };
}

// Usage
const headers = signRequest(tokenId, secret, 'POST', '/orders', JSON.stringify(orderPayload));
const res = await fetch('https://api.limitless.exchange/orders', {
  method: 'POST',
  headers: { ...headers, 'Content-Type': 'application/json' },
  body: JSON.stringify(orderPayload),
});
Example (Python):
import hmac, hashlib, base64
from datetime import datetime, timezone

def sign_request(token_id: str, secret: str, method: str, path: str, body: str = "") -> dict:
    timestamp = datetime.now(timezone.utc).isoformat()
    message = f"{timestamp}\n{method}\n{path}\n{body}"

    signature = base64.b64encode(
        hmac.new(
            base64.b64decode(secret),
            message.encode("utf-8"),
            hashlib.sha256,
        ).digest()
    ).decode("utf-8")

    return {
        "lmts-api-key": token_id,
        "lmts-timestamp": timestamp,
        "lmts-signature": signature,
    }

Getting a Scoped API Token

Most builders should generate a token from the UI. The programmatic flow below it is for headless / CI environments where you can’t open a browser.
1

Connect your wallet

Go to limitless.exchange and connect your wallet. No smart wallet required.
2

Derive a token

Open the API token modal → API Tokens tab → Derive. Copy the tokenId and secret. The browser handles the login session for you, so there’s no identity token to copy by hand.
3

Use the credentials

Pass them to the SDK as hmacCredentials (TypeScript), hmac_credentials (Python), or WithHMACCredentials (Go) — the SDK signs every request automatically. This is a one-time setup.

Programmatically (headless / CI)

When you can’t use a browser, call the same endpoint the UI calls:
1

Obtain a Privy identity token

Authenticate with Privy and capture the token field from the authenticate response (not privy_access_token).
2

Derive a token

POST /auth/api-tokens/derive with the identity token in the identity header as Bearer <token>. The response includes a tokenId and secret. Use these HMAC credentials for all subsequent requests.
The trading scope is available to all users — no application required. For partner-level scopes (account_creation, delegated_signing), apply for programmatic API access to get your account enabled.
The token secret is returned once at creation time. Store it securely — it cannot be retrieved again.

Delegated Signing

With the delegated_signing scope, partners can submit orders without providing signature and signatureType in the order payload. The server signs the order using a Privy server wallet linked to the target sub-account. Use the onBehalfOf field in the order payload to specify which sub-account the order is for. The target profile must be linked to your partner account.

Checksummed Addresses

All Ethereum addresses in API requests must use checksummed format (EIP-55 mixed-case):
  • x-account header
  • maker and signer fields in orders
Example: 0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed
Your private key is still required for EIP-712 order signing (unless using delegated signing), but the scoped token handles request authentication.

Legacy API Keys

API keys are deprecated and are no longer issued to new users. If you have an existing API key, it will continue to work, but we recommend migrating to scoped API tokens for improved security and access control.
If you hold a legacy static API key, it still works for now, but migrate to scoped API tokens (HMAC) above — they add request signing, scopes, and expiry. New keys are no longer issued, and all examples in these docs use HMAC.