API keys
Call the REST API from a script, a server or another app with a personal API key.
Every user can create API keys and call the REST API under /api/v1. A key acts as the user who created it: it sees the same account and the same organisations.
The examples use the demo at https://demo.launch-now.dev. On your own deployment, replace it with your NEXT_PUBLIC_APP_URL.
1. Create a key
- Sign in and open Account → API keys (
/account/keys). - Click Create key, give it a name (for example
ci-script) and an optional expiry, from 1 to 365 days. - Copy the key right away. It starts with
pulse_and is shown only once. Afterwards the list only shows its first characters.
Store it like a password: in an environment variable or a secret manager, never in the repository.
export PULSE_API_KEY="pulse_…"2. Call the API
Send the key in the x-api-key header.
curl https://demo.launch-now.dev/api/v1/me \
-H "x-api-key: $PULSE_API_KEY"{
"data": {
"id": "u_8f2c…",
"name": "Ada Lovelace",
"email": "ada@example.com",
"createdAt": "2026-09-01T10:12:00.000Z"
}
}The same call from TypeScript:
const response = await fetch("https://demo.launch-now.dev/api/v1/me", {
headers: { "x-api-key": process.env.PULSE_API_KEY! },
})
if (!response.ok) throw new Error((await response.json()).error.message)
const { data } = await response.json()Endpoints
| Method | Path | Returns |
|---|---|---|
GET | /api/v1/me | The account that owns the key |
GET | /api/v1/organizations | The organisations the owner belongs to, with role |
curl https://demo.launch-now.dev/api/v1/organizations \
-H "x-api-key: $PULSE_API_KEY"{
"data": [
{
"id": "org_41d…",
"name": "Acme",
"slug": "acme",
"role": "owner",
"createdAt": "2026-09-02T08:00:00.000Z"
}
]
}Every response is JSON. Successful responses wrap the result in data.
Errors
Errors always come back as JSON with a stable code:
{
"error": {
"code": "invalid_api_key",
"message": "This API key is invalid, disabled or expired.",
"docs": "https://demo.launch-now.dev/docs/api"
}
}| Status | Code | What to do |
|---|---|---|
401 | missing_api_key | Add the x-api-key header |
401 | invalid_api_key | The key is wrong, disabled or expired: create a new one |
429 | rate_limited | Wait a minute, then retry |
404 | not_found | Check the path against the endpoint table |
Limits
Each key allows 60 requests per minute. The limit is set in lib/auth.ts, in the apiKey() plugin (rateLimit), and applies to keys created after a change.
Rotate or revoke a key
- Rotate: create a new key, deploy it where the old one was used, then delete the old one. Nothing breaks in between.
- Pause: Disable a key to reject its requests without deleting it, and Enable it again later.
- Revoke: Delete it. Requests with that key fail with
invalid_api_keyimmediately.
Add your own endpoint
Wrap the route handler in withApiKey, which checks the key and gives you the caller:
import { withApiKey } from "@/lib/api/with-api-key"
export const GET = withApiKey(async ({ userId }) => {
const projects = await listProjectsFor(userId)
return Response.json({ data: projects })
})Unknown paths under /api/v1 already answer with a JSON 404, so clients never receive an HTML page.