Authentication
API key auth, JWT auth, OAuth, and all auth endpoints
Authentication
PAPI uses two authentication systems:
- API keys -- For SDK and programmatic access to market data and trading endpoints
- JWT tokens -- For dashboard sessions and account management endpoints
Both are sent as Bearer tokens in the Authorization header.
API Key Authentication
Getting an API Key
- Sign up at dashboard.tylerthebuildor.com
- Create an API key from the dashboard
- Save the key immediately -- it is shown only once
Each user is limited to one API key.
Using an API Key
curl https://papi.tylerthebuildor.com/polymarket/markets \
-H "Authorization: Bearer papi_sk_live_a1b2c3d4e5f6..."Scopes
API keys are scoped to control access:
| Scope | Access |
|---|---|
read | Market data endpoints: markets, events, orderbooks, trades, candlesticks |
trade | Trading endpoints: orders, positions, balance. Implies read. |
admin | All endpoints |
Attempting to access a trading endpoint with a read-only key returns a 403 forbidden error.
How Keys Are Stored
API keys are SHA-256 hashed before storage. The raw key is never persisted. On each request, the provided key is hashed and matched against stored hashes.
Verify Your Key
curl https://papi.tylerthebuildor.com/me \
-H "Authorization: Bearer papi_sk_live_a1b2c3d4e5f6..."{
"user_id": "usr_abc123",
"key_prefix": "papi_sk_live_a1b2",
"scopes": ["read", "trade"]
}JWT Authentication
JWT tokens are used by the dashboard for browser-based sessions. They are issued by the auth endpoints below.
The access token is returned in the response body. The refresh token is set as an HTTP-only cookie.
Auth Endpoints
All auth endpoints are public (no Authorization header required), except /auth/logout.
Sign Up
POST /auth/signupcurl -X POST https://papi.tylerthebuildor.com/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "securepassword123",
"name": "Jane Doe"
}'Response depends on the current access mode:
Whitelist mode (email pre-approved):
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "usr_abc123",
"email": "user@example.com",
"name": "Jane Doe",
"avatar_url": null,
"role": "user"
}
}Open mode (email verification required):
{
"message": "Verification email sent",
"user_id": "usr_abc123"
}Log In
POST /auth/logincurl -X POST https://papi.tylerthebuildor.com/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "securepassword123"
}'Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "usr_abc123",
"email": "user@example.com",
"name": "Jane Doe",
"avatar_url": null,
"role": "user"
}
}A refresh_token cookie is also set (HttpOnly, Secure, SameSite=Strict).
Verify Email
POST /auth/verify-emailcurl -X POST https://papi.tylerthebuildor.com/auth/verify-email \
-H "Content-Type: application/json" \
-d '{ "token": "verification-token-from-email" }'{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "usr_abc123",
"email": "user@example.com",
"name": "Jane Doe",
"avatar_url": null,
"role": "user"
}
}Forgot Password
POST /auth/forgot-passwordcurl -X POST https://papi.tylerthebuildor.com/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{ "email": "user@example.com" }'Always returns 200 regardless of whether the email exists:
{
"message": "If that email exists, a reset link has been sent"
}Reset Password
POST /auth/reset-passwordcurl -X POST https://papi.tylerthebuildor.com/auth/reset-password \
-H "Content-Type: application/json" \
-d '{
"token": "reset-token-from-email",
"new_password": "newsecurepassword456"
}'{
"message": "Password reset successfully"
}Google OAuth Callback
POST /auth/google/callbackcurl -X POST https://papi.tylerthebuildor.com/auth/google/callback \
-H "Content-Type: application/json" \
-d '{
"code": "google-auth-code",
"code_verifier": "pkce-code-verifier",
"redirect_uri": "https://dashboard.tylerthebuildor.com/auth/callback"
}'{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "usr_abc123",
"email": "user@example.com",
"name": "Jane Doe",
"avatar_url": "https://...",
"role": "user"
}
}GitHub OAuth Callback
POST /auth/github/callbackSame request/response shape as the Google callback.
Refresh Token
POST /auth/refreshReads the refresh_token from the request cookie. No request body needed.
curl -X POST https://papi.tylerthebuildor.com/auth/refresh \
-b "refresh_token=..."{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "usr_abc123",
"email": "user@example.com",
"name": "Jane Doe",
"avatar_url": null,
"role": "user"
}
}Log Out
POST /auth/logoutRequires authentication. Reads the refresh token cookie and invalidates it.
curl -X POST https://papi.tylerthebuildor.com/auth/logout \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-b "refresh_token=..."{
"message": "Logged out successfully"
}The refresh_token cookie is cleared in the response.
Exchange Credentials
After authenticating with PAPI, the API uses your stored exchange credentials to proxy trading requests. Credentials are:
- Encrypted with AES-256-GCM at the application layer
- Stored in PostgreSQL
- Decrypted only at request time, in memory
- Never logged or exposed in API responses
Credential management is done via the Account endpoints.
Security Best Practices
- Store API keys in environment variables -- never hardcode them
- Use the minimum scope needed (
readfor market data,tradeonly if placing orders) - Revoke and regenerate keys if compromised
- Monitor
last_used_aton your API key for unauthorized usage - Exchange credentials are encrypted at rest, but treat the PAPI API key as the master secret