[ Quickstart ]
Three steps, about two minutes.
Everything is read-only, so there is nothing you can break while exploring.
01
Create a key
At app.scriben.ai, under Settings → Developer. Copy it immediately — we store only a hash, so we genuinely cannot show it to you twice.
02
Confirm it works
/v1/me exists for exactly this. It answers whether the key is live, and
nothing else.
03
Follow a ref
List notes, then use a returned ref to read one. Refs are the only way to
address a note.
export SCRIBEN_API_KEY="scb_live_…" $ curl https://public-api.scriben.ai/v1/notes?limit=3 \ -H "Authorization: Bearer $SCRIBEN_API_KEY" $ curl https://public-api.scriben.ai/v1/notes/n_a41f9c2b0e17 \ -H "Authorization: Bearer $SCRIBEN_API_KEY"
const res = await fetch( "https://public-api.scriben.ai/v1/notes?limit=3", { headers: { Authorization: `Bearer ${process.env.SCRIBEN_API_KEY}` } }, ); if (!res.ok) throw new Error((await res.json()).error.code); const { data, next_cursor } = await res.json(); // next_cursor is opaque — hand it back verbatim, never parse it.
import os, httpx r = httpx.get( "https://public-api.scriben.ai/v1/notes", params={"limit": 3}, headers={"Authorization": f"Bearer {os.environ['SCRIBEN_API_KEY']}"}, ) r.raise_for_status() notes = r.json()["data"]
[ Authentication ]
One header.
Keys are never accepted in a query parameter. URLs reach access logs, browser history, referrer headers and error trackers — a credential that leaks by being logged leaks everywhere at once.
AuthorizationBearer scb_live_… on every request.scb_<env>_<43>The vendor prefix is what GitHub and commercial secret scanners match on, so a committed key can be found and revoked automatically.401Missing, malformed, revoked and expired keys are reported identically — the error never confirms whether a key was real.live · testThe environment is part of the key, so a test credential pasted into production fails loudly rather than reading real meetings.[ Conventions ]
The same rules on every endpoint.
Learn them once.
Pagination is cursor-based
Pass limit (1–50) and follow next_cursor until has_more
is false. Cursors are opaque — do not construct or parse one. Offsets are computed against a
list that changes: record a note between page one and page two and every row shifts, so the
caller silently re-reads one item and never sees another. A cursor names where it stopped.
let cursor = null, all = []; do { const url = new URL("https://public-api.scriben.ai/v1/notes"); url.searchParams.set("limit", 50); if (cursor) url.searchParams.set("cursor", cursor); const page = await (await fetch(url, { headers })).json(); all.push(...page.data); cursor = page.next_cursor; } while (cursor);
Errors carry a stable code
Branch on code, never on message — the code is part of the contract,
the prose is not. Every error carries a request_id; quote it and we can find the
exact request.
{
"error": {
"code": "not_found",
"message": "No note with that ref.",
"request_id": "req_8f2ad41c9b07"
}
}
| Code | Status | Meaning |
|---|---|---|
| unauthorized | 401 | Key missing, malformed, revoked or expired. |
| invalid_request | 400 | A parameter is missing or malformed, including an undecodable cursor. |
| not_found | 404 | No such resource for this account. |
| method_not_allowed | 405 | The API is read-only. |
| internal | 500 | Our fault. Quote the request id. |
Refs, not database ids
Notes are addressed by an opaque per-account ref such as n_a41f9c2b0e17. The same
note has a different ref in a different account, so a ref cannot correlate anything across
accounts. A ref issued to someone else returns 404 — the same answer an invented one gets.
Every answer shows its work
as_of is on every response, and reads derived from meetings carry
source_notes — the notes behind the answer, so a claim about a person is one
you can check rather than one you have to trust.
[ Reference ]
Seven endpoints. All of them read.
The full schema is published as OpenAPI 3.1, which is what the SDKs are generated from.
GET /v1/meConfirm a key works.GET /v1/notesNewest first. Titles, dates and participants — never a transcript.GET /v1/notes/{ref}Summary and action items for one note.GET /v1/notes/{ref}/transcriptMatching passages with speaker and timestamp. A query is required.GET /v1/action-itemsAcross notes, each with the note it came from. Filter by person.GET /v1/memoriesDurable facts and decisions, honouring private and team visibility.GET /v1/peopleWhat the account knows about someone.An ambiguous name is a result, not an error
If two people share a first name, /v1/people returns
status: "ambiguous" with candidates and a 200. Blending two people into one
confident answer is the failure we refuse to ship — call again with the ref you meant.
[ Clients ]
Generate the client, don't write it.
The schema at docs.scriben.ai/openapi.json is generated from the server's own route table, not maintained beside it — a test fails the build if the two disagree. Point a generator at it and the types are the API's types.
$ npx @hey-api/openapi-ts \ -i https://docs.scriben.ai/openapi.json \ -o src/scriben
$ npx @openapitools/openapi-generator-cli generate \ -i https://docs.scriben.ai/openapi.json \ -g python -o ./scriben
$ go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest \ -package scriben \ https://docs.scriben.ai/openapi.json > scriben.go
We do not publish hand-written SDKs, and that is a choice rather than a gap. A generated
client is correct the day the schema changes; a hand-written one is correct until somebody
forgets. If you would rather not generate anything, seven endpoints and one header is
an afternoon's worth of fetch.
[ Versioning ]
How this changes, and how it won't.
The question worth answering before you build on something: what can break you, and what cannot.
/v1 is frozenNo field in /v1 will change meaning or disappear. A change that would require editing working code gets a new path, not a new release note.additive, unannouncedNew endpoints, new response fields and new error codes can land at any time. Parse tolerantly: ignore fields you do not know rather than rejecting the response.codes over proseError message text is written for a human reading a log and may be reworded. Only code is a contract.the schema is the truthWhere this page and openapi.json disagree, the schema is right — it is generated from the running server.[ Boundaries ]
What it deliberately will not do.
An interface to your meeting history is only worth having if its limits are exact.
Read-only
Nothing in v1 mutates anything. There is no write path in the surface at all.
Passages, not transcripts
Search returns matching excerpts with speaker and timestamp. Bulk export of everything anyone said is not a feature we want to have built.
One account per key
A key reads exactly one account and cannot be widened. There is no org or team scope to escalate into.
Flagged content withheld
Text that reads as an instruction aimed at an assistant returns a reason instead of the prose, so a poisoned note cannot steer your agent.
Polling, for now
There are no outbound webhooks yet, so a workflow that reacts to a finished meeting has to ask. Signed delivery is designed and next.
No audio
Recordings are not reachable through the API. It is the most-requested endpoint and it is blocked on storage work, not on the interface.
[ Which surface ]
API or MCP.
They are not competing, and they are not for the same job.
Use the API
When you are building something — an agent, a scheduled digest, an internal dashboard. You own the loop: model, prompts, scheduling and storage are yours.
Use MCP
When you want Scriben inside Claude, ChatGPT, Cursor or Zed. One command, no code, and the assistant decides which tool it needs.