Documentation
Reference · Authentication
Authentication
Basis has two ways to authenticate: a person signs in to the dashboard with Privy (wallet or email), and an agent or app calls the API with a sk-basis- key it minted in that dashboard. Public pages need no login at all. This page covers login, the dashboard, API keys, how the server verifies a Privy token without any app secret, linked wallets, and the honest pending posture.
Overview
Reading Basis is open. The home page, these docs, the network data, the inference, workers, receipts, settlement, token, and Bankr pages all render with no account. You only sign in to manage your own things — API keys, credits, receipts, and worker status — in the dashboard.
People → Privy login
Sign in with a wallet or an email. The dashboard then shows your identity, linked wallet, credits, keys, receipts, and worker.
Agents → API keys
An sk-basis- key in the Authorization header. Minted once in the dashboard, used like an OpenAI key.
Privy login
Sign-in is handled by Privy. On basis.watch you can authenticate either by connecting a wallet or with an email — both resolve to the same account, identified by a Privy DID (decentralized identifier). The DID is the stable handle for your account; a linked wallet and email are accounts attached to it.
On success Privy issues a short-lived access token (a JWT). The browser sends it to Basis on protected requests, and the server verifies it (see token verification). An optional identity token carries the list of linked accounts when the app needs it.
The dashboard
Once signed in, /dashboard is your account home. It shows your Privy DID, your linked wallet, your $BASIS credit balance, your API keys (by prefix), your receipts, and your worker status. Each area has its own page.
- /dashboardAccount overview — DID, linked wallet, credits, and a summary of keys, receipts, and worker.
- /dashboard/api-keysMint, list (by prefix), and revoke sk-basis-... API keys.
- /dashboard/billingYour credit balance and how to fund it (deposit $BASIS, or route ETH/WETH/USDC) — for a linked wallet.
- /dashboard/receiptsThe inference receipts your account has produced, each verifiable by hash.
- /dashboard/workerYour worker status and accrued rewards, mapped to your linked worker wallet.
API keys
An authenticated user mints API keys in the dashboard. A Basis key is prefixed sk-basis- and is used exactly like an OpenAI key — a bearer token in the Authorization header.
Authorization: Bearer sk-basis-...
Shown once
When you create a key, the raw sk-basis-... value is displayed exactly once — copy it then. Basis never stores the raw key; it keeps only a peppered hash of it (BASIS_API_KEY_PEPPER, a server-only secret). If you lose a key, you cannot recover it — revoke it and mint a new one.
# Mint an API key with a Privy access token (the dashboard does this for you).
# The raw sk-basis-... value is returned ONCE — store it immediately.
curl -X POST https://basis.watch/api/user/api-keys \
-H "Authorization: Bearer <PRIVY_ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"label":"my-agent"}'List and revoke
Keys are listed by their prefix only — never the full secret — so you can identify a key without it ever leaving the server again. Any key can be revoked; a revoked key stops authenticating immediately.
Use a key
# Call inference with the API key in the Authorization header.
curl https://basis.watch/api/v1/chat/completions \
-H "Authorization: Bearer sk-basis-..." \
-H "Content-Type: application/json" \
-d '{"model":"basis-default","messages":[{"role":"user","content":"Ready when funded."}]}'Key issuance activates once BASIS_API_KEY_PEPPER is configured. The full API-key flow — header usage, the create route, and the auth posture — is also documented in the API reference.
Token verification
The server verifies a Privy access token against Privy's public JWKS using jose. The token is a JWT; verification checks its signature against the published signing keys and asserts the issuer is privy.io, the audience is the app ID, and the subject (sub) is the user's DID.
No app secret in the verify path
Verification needs only the public JWKS — there is no app secret anywhere in the token-verification path. The Privy app secret (PRIVY_APP_SECRET) is server-only and used only for advanced Privy server API calls; it is never NEXT_PUBLIC and never shipped to the browser.
// The server verifies a Privy ACCESS token against the public JWKS with jose.
// No app secret is in the verification path — only the public signing keys.
import { jwtVerify, createRemoteJWKSet } from "jose";
const JWKS = createRemoteJWKSet(new URL(process.env.PRIVY_JWKS_URL!));
const { payload } = await jwtVerify(accessToken, JWKS, {
issuer: "privy.io",
audience: process.env.PRIVY_APP_ID, // the app ID is the audience
});
const did = payload.sub; // the Privy DID identifies the userAn optional identity token can carry the user's linked accounts (the wallets and email attached to the DID) when a route needs to read them.
Linked wallets
A wallet must be linked to your account before you can act for it. This binds money movement and worker rewards to wallets you actually control.
Payment binding
You may only quote or pay for a linked wallet. /api/payments/prepare and /api/payments/confirm require authentication and a linked payer wallet, so credits fund the wallet you signed in with. See credits.
Worker binding
When authentication is required, worker registration requires a linked worker_wallet, and accrued rewards map to that wallet and your account. See the worker guide.
When auth becomes required
Whether authentication is enforced is controlled by BASIS_AUTH_REQUIRED(default false). In pre-launch, anonymous calls still work — they return the same runtime_pending response as everything else while the backend is absent.
When BASIS_AUTH_REQUIRED is true, /api/v1/chat/completions and the user routes require a Privy token or an API key. Auth becomes required when Basis goes live — plan for it now by minting a key, even though one is only accepted (not required) today.
Auth error shapes
Auth failures carry a structured { error: { code } }. Branch on error.code, not the message.
| Status | Code | When |
|---|---|---|
| 401 | auth_required | A protected route was called with no Privy token and no API key, while authentication is required. |
| 401 | invalid_privy_token | A Privy access token was supplied but failed JWKS verification (bad signature, wrong issuer/audience, or expired). |
| 403 | wallet_not_linked | An authenticated user tried to quote, pay, or register a worker for a wallet that is not linked to their account. |
| 503 | privy_config_pending | Login was requested before NEXT_PUBLIC_PRIVY_APP_ID is configured. The auth surface is live; Privy is honestly pending. |
Pending posture
Authentication ships honestly: the surface is real, but the pieces that depend on configuration are marked pending until that configuration lands.
- pending
Login activates once configured
Privy login becomes available once NEXT_PUBLIC_PRIVY_APP_ID is set. Until then login is honestly pending (503 privy_config_pending), not a broken button.
- pending
API keys activate with the pepper
Key issuance and verification activate once BASIS_API_KEY_PEPPER is configured. The peppered-hash design is in place; the secret is server-only.
- pending
Privy app is in development mode
The Privy app is in development mode (<=150 users). Upgrade it to production and configure basis.watch as the domain before public launch.
No custody, no secret in the client
Basis never holds your keys or funds. Login proves who you are; a linked wallet is yours to sign with. No app secret and no API-key pepper is ever embedded in the client bundle. soon