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"plusscopenaming the missing permission (notes.write/notes.review)
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-160filter + 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).
=, !=, <, <=, >, >=; : (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
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 requireIdempotency-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.
