Skip to main content
The authoritative contract is OpenAPI 3.1: GET /openapi.json, committed at contracts/openapi/elanotes.v1.json, copied to this site’s openapi.json. Generated reference pages sit under API reference. Style authority is Google AIP. Recorded deviations: envelope outer shape, vault-implicit paths, Idempotency-Key header, receipts instead of AIP-151 Operations. There is no /v1/daily, /v1/tasks, or folder_id. Daily notes are filter=kind = "daily". Tasks are checklist items in note bodies. Folders are vault-relative path strings.

Host path

/healthz returns {"status":"ok"}. /openapi.json returns 200. Unauthenticated /v1/vault is 401 AUTH_MISSING, not 503. The API reference server list is production, then local. /v1 is the stable path. GET /openapi.json and GET /healthz are unauthenticated. Auth protects only /v1 and /mcp.

Auth and WWW-Authenticate

Send Authorization: Bearer <token>. Open locally when no auth is configured. The vault is implicit: the bearer binds vault_id. Resource bodies may carry AIP-122 name (vaults/{vault_id}/notes/{slug}); paths stay /v1/notes/{note}. 401 / 403 on /v1 and /mcp add RFC 6750 WWW-Authenticate: Bearer realm="elanotes":
  • missing token → no error= pair (challenge only)
  • presented but invalid → error="invalid_token"
  • forbidden → error="insufficient_scope" plus scope naming the missing permission (notes.write / notes.review)
When WORKOS_CLIENT_ID or OAUTH_TEST_HMAC_SECRET is set, the challenge includes resource_metadata and GET /.well-known/oauth-protected-resource serves RFC 9728 metadata. Otherwise that well-known path is 404 with a hint.

Errors, reasons, hints

Envelope:
error, message, statusCode, requestId keep their historic meaning. reason is UPPER_SNAKE. hint is one imperative sentence naming the next request. help is this site’s errors page with a lowercase-reason anchor. details is the AIP-193 list (ErrorInfo, BadRequest, RetryInfo, QuotaFailure, Help). Follow hint.

Filters

AIP-160 filter + AIP-132 order_by on GET /v1/notes, /v1/search, /v1/grep, /v1/writes, and /v1/notes/{note}:listRevisions. GET /v1/tags rolls up frontmatter tags (body #hashtags are not tags).
Comparators =, !=, <, <=, >, >=; : (has) for repeated fields; AND, OR, NOT / -; parentheses; double-quoted strings; RFC 3339 timestamps; * wildcard inside string literals. Note fields: slug, path, folder, title, tags, kind (regular | daily), status (active | archived), visibility, daily_date, created_at, updated_at, observed_at. Pending-write fields: status, kind, agent_id, created_at, decided_at. Unknown field or operator → 400 INVALID_FILTER with a hint listing allowed fields. Coverage echoes filter. POST /v1/query stays for property aggregation. The list path is GET /v1/notes?filter=.

Field masks

AIP-161 paths: title, content, folder, tags, status, frontmatter.<key>. Body is the partial resource. Missing or unknown mask → 400 INVALID_UPDATE_MASK. Writes still need Idempotency-Key and honor review / budget.

Pagination

Canonical AIP-158: page_size, page_token, next_page_token. Legacy limit / cursor / next_cursor / prefix / archived / status stay accepted and emitted through v1 and are marked deprecated in the spec. Default order stays deterministic. order_by examples: updated_at desc, created_at, observed_at, title, daily_date desc.

Rate limits

Abuse protection, not cost metering. Reads are never metered. 0 disables a policy. Responses carry IETF draft RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset. Every 429 includes Retry-After. Envelope reason is RATE_LIMITED (abuse) or WRITE_BUDGET_EXCEEDED (per-agent budget, separate quota).

Idempotency

Writes require Idempotency-Key. Replay with the same key and body returns the original receipt (idempotency-replayed: true). A different body with the same key is 409 IDEMPOTENCY_KEY_REUSED. SDKs and the CLI mint a UUID unless you supply one.

Custom methods

Canonical AIP-136 form (colon verb): Revisions expose AIP-162 revision_id / revision_create_time and keep version_id as a v1 alias. Create / PATCH receipts embed note when status = "applied". Queued receipts are GET /v1/writes/{id}.

Deprecations

Legacy slash forms (/edit, /move, /rename, /restore, /approve, /reject, /versions) and PUT /v1/vault/write-settings remain through v1. They answer with Deprecation: true and Link: <canonical>; rel="successor-version". Prefer the colon verbs and PATCH + update_mask.