API Overview
The Everscribe HTTP API is what the SDKs and the es CLI are built on. Use it directly when you're scripting against Everscribe from a shell, or writing a client for a language we don't ship an SDK for.
This reference covers the developer surface: projects, API keys, events, and integrations. Billing, plan, and sharing endpoints aren't part of the public API.
Base URL
https://api.everscribe.io
Every path in this reference starts with /v1. Request and response bodies are JSON (Content-Type: application/json).
Authentication
The API takes two kinds of credential, and each endpoint accepts exactly one of them. Both go in the Authorization header as a bearer token.
| Credential | Looks like | Used for |
|---|---|---|
| Personal access token (PAT) | pat_ + 32 hex characters |
Everything except ingest: projects, API keys, reading events, integrations. |
| Project API key | evs_ + 32 hex characters |
Ingest: recording events into the key's own project. |
Sending the wrong one gets a 401. A PAT on an ingest endpoint is rejected, and so is a project key on any other endpoint.
Personal access tokens
A PAT acts as you. Get one either way:
es auth login. The CLI runs a browser sign-in and saves the token to~/.config/everscribe/pat.json. It expires after 90 days. See CLI auth.- Developer Settings. Mint a token with a label and an expiry of your choosing, or none. The plaintext is shown once.
curl https://api.everscribe.io/v1/projects \
-H "Authorization: Bearer $EVERSCRIBE_PAT"
A PAT carries all of your permissions. There are no scopes and no per-project limits, so it can do anything you can do on the projects you can reach, including minting project API keys that never expire. Treat it like a password: keep it out of source control and revoke it from Developer Settings if it leaks. A PAT can't create, list, or revoke PATs; that's only possible from Developer Settings.
Project API keys
A project API key belongs to one project, and in this reference it's only used to record events into that project. It's the same key the SDKs use (they also use it to mint embed tokens). Mint one with your PAT (Create a key) or from the dashboard.
curl -X POST https://api.everscribe.io/v1/projects/$PROJECT_ID/events \
-H "Authorization: Bearer $EVERSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"id":"0b6f8a3e-6f43-4a0e-9a53-1f2d0c4b7e11","action":"user.login","actor":{"type":"user","id":"u_42"}}'
From zero to a recorded event
The sequence a new script or client follows: create a project with your PAT, mint an ingest key for it, send an event with the key, then read it back with the PAT.
API=https://api.everscribe.io
# 1. Create a project (PAT)
PROJECT_ID=$(curl -s -X POST $API/v1/projects \
-H "Authorization: Bearer $EVERSCRIBE_PAT" \
-H "Content-Type: application/json" \
-d '{"name":"billing-service"}' | jq -r .project.id)
# 2. Mint an ingest key (PAT). The plaintext is only returned here.
EVERSCRIBE_API_KEY=$(curl -s -X POST $API/v1/projects/$PROJECT_ID/keys \
-H "Authorization: Bearer $EVERSCRIBE_PAT" \
-H "Content-Type: application/json" \
-d '{"name":"production"}' | jq -r .plaintext)
# 3. Record an event (project key)
curl -s -X POST $API/v1/projects/$PROJECT_ID/events \
-H "Authorization: Bearer $EVERSCRIBE_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"id\":\"$(uuidgen | tr A-Z a-z)\",\"action\":\"invoice.paid\",\"actor\":{\"type\":\"user\",\"id\":\"u_42\"}}"
# 4. Read it back (PAT)
curl -s "$API/v1/projects/$PROJECT_ID/events?limit=1" \
-H "Authorization: Bearer $EVERSCRIBE_PAT"
Permissions
Endpoints that act on a project check your role on that project:
| Role | Who has it |
|---|---|
admin |
You, on every project you own. Collaborators you've shared a project with as admin. |
editor |
Collaborators shared as editor. |
viewer |
Collaborators shared as viewer. |
Each endpoint lists the minimum role it needs. Asking for a project you have no access to returns 404, not 403, so the response doesn't reveal whether the project exists. Having access but too low a role returns 403. See Collaborators for how sharing works.
Errors
Errors are reported with the HTTP status code. The response body is a short plain-text message (Content-Type: text/plain), not JSON. The message is for humans and may change, so branch on the status code.
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
project not found
| Status | Meaning |
|---|---|
400 |
The request is malformed or fails validation. The body says what's wrong, e.g. name is required or invalid request body. |
401 |
The credential is missing, malformed, expired, revoked, or the wrong kind for this endpoint. The body is unauthorized. |
402 |
Your plan doesn't allow it, e.g. one integration over your plan's limit. |
403 |
You can reach the resource but your role is too low. The body is forbidden. |
404 |
The resource doesn't exist, you don't have access to it, or the ID in the path isn't a valid UUID. |
409 |
A name is already taken, or an event id is already used in another project. |
500 |
Something failed on our side. Safe to retry with backoff. |
One exception to plain-text bodies: an unparseable query expression on the event list returns a JSON body, so a client can point at the offending token.
Timestamps
Timestamps are RFC 3339 strings in UTC, e.g. 2026-10-30T14:03:11.402Z. A timestamp that hasn't been set yet, like a key's last_used_at before its first use, comes back as the zero time 0001-01-01T00:00:00Z rather than being left out or null.
Pagination
Only the event list paginates. It returns a next_cursor; pass it back as ?cursor= to get the next page. Every other list returns all items in one response.