API Conventions¶
About this document
Audience: agentic tools implementing endpoints; external-service developers integrating with the API.
Purpose: define the rules every API endpoint follows, so that any single endpoint can be understood by reading only this document plus its specific resource shape.
Companion documents: Data Model for the underlying entities; Architecture for the system design behind these conventions.
1. Foundations¶
1.1 Style¶
REST over HTTPS. JSON request and response bodies. English-only response copy.
1.2 Base URL¶
All endpoints are mounted under a versioned prefix:
The /api segment is the Caddy reverse-proxy prefix; the /v1 segment is the API version.
1.3 Versioning¶
URI-based versioning. Per N-API-002:
v1is committed-stable. Only additive changes once shipped (new endpoints, new optional fields).- Breaking changes ship as
v2, withv1supported in parallel for a documented grace period. - Deprecation is signaled via
DeprecationandSunsetHTTP headers (RFC 8594).
A self-hosted instance pins to a specific server version; clients pin to a specific API major version.
1.4 Content Types¶
- Requests:
Content-Type: application/jsonfor all bodies.multipart/form-dataonly for file uploads. - Responses:
Content-Type: application/json; charset=utf-8.
1.5 Character Encoding¶
UTF-8 throughout. Non-ASCII names, notes, and identifiers are first-class.
2. URL Conventions¶
2.1 Resource Naming¶
- Plural, lowercase, kebab-case:
/families,/accounts,/transactions,/api-keys. - Nested resources under their parent:
/families/{familyId}/accounts/{accountId}/transactions/{transactionId}. - Singletons (per-current-user):
/me,/me/sessions,/me/recovery-codes.
2.2 Path Parameters¶
Always UUIDv7. Server validates format; invalid IDs return 400 Bad Request with code INVALID_ID_FORMAT.
2.3 Family Scoping¶
Every family-scoped operation includes {familyId} in the path:
GET /v1/families/{familyId}/accounts
POST /v1/families/{familyId}/accounts
GET /v1/families/{familyId}/accounts/{accountId}
PATCH /v1/families/{familyId}/accounts/{accountId}
DELETE /v1/families/{familyId}/accounts/{accountId}
The auth middleware verifies the caller's role in {familyId} before the route handler runs.
2.4 Query Parameters¶
- Filter:
?from=2026-01-01&to=2026-04-25&category_id=...&account_id=... - Pagination:
?cursor=<opaque>&limit=50(see §5). - Sorting:
?sort=field.direction(e.g.,?sort=date.desc). Multiple sorts comma-separated:?sort=date.desc,amount.asc. - Field selection: not supported in MVP. (Defer; revisit if responses get bloated.)
2.5 Reserved Verbs¶
REST verbs are used in the standard way:
| Verb | Use |
|---|---|
| GET | Read; safe; idempotent. Never has a body. |
| POST | Create; not idempotent without Idempotency-Key. |
| PATCH | Partial update; idempotent with Idempotency-Key and version. |
| PUT | Full replacement. Used sparingly (e.g., setting tags on a transaction). |
| DELETE | Soft-delete (or hard-delete for hard-deletable entities). Idempotent. |
Action endpoints that don't fit CRUD use a sub-resource verb noun: POST /transactions/{id}/clear, POST /invitations/{id}/accept. Avoid ?action=... query parameters.
3. HTTP Headers¶
3.1 Required Request Headers¶
| Header | Required for | Purpose |
|---|---|---|
Authorization: Bearer <jwt> |
All user-authenticated endpoints | Access token. |
X-API-Key: <key> |
All external-service endpoints | API key (alternative to Authorization). |
X-Operator-Token: <token> |
POST /v1/operator/session bootstrap only |
Operator bootstrap credential. |
X-Acting-User-Id: <user_id> |
External-service requests acting on behalf of a user | Required when key is writer-scoped and the action requires a user. |
X-Family-Id: <family_id> |
When the family is not in the path | Rare; most endpoints have it in the path. |
Idempotency-Key: <key> |
Recommended for all mutations | See §6. |
If-Match: <version> |
Recommended for PATCH/PUT/DELETE on versioned entities |
Alternative to in-body version. See §7. |
Content-Type: application/json |
Requests with a body |
3.2 Standard Response Headers¶
| Header | Set on | Meaning |
|---|---|---|
X-Request-Id: <uuid> |
All responses | The unique request ID; matches structured log entries. |
Deprecation: true |
Deprecated endpoints | Per RFC 8594. |
Sunset: <http-date> |
Deprecated endpoints | When the endpoint will be removed. |
Retry-After: <seconds> |
429, 503 responses | When the client should retry. |
RateLimit-* |
Rate-limited endpoints | Per IETF draft (RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset). |
4. Request & Response Bodies¶
4.1 Request Body Conventions¶
- All field names are
snake_case(matching the Data Model). - Fields not present in the body are unchanged on
PATCH(true partial update). - Explicit
nullclears a nullable field. - Unknown fields are rejected with
400 Bad Requestand codeUNKNOWN_FIELD(strict mode; protects against client mistakes).
4.2 Response Body Conventions¶
- All field names are
snake_case. - Single-resource responses return the resource object directly:
- List responses are wrapped:
- Counts and aggregations are wrapped under named fields, never bare numbers:
4.3 Money Representation in JSON¶
Money is always represented as two fields:
Where minor is an integer in the currency's smallest unit, and currency is the ISO 4217 (or extended) code. Never as a decimal number.
For display formatting, the client looks up decimal_places from the currency definition and renders accordingly.
4.4 Quantity Representation¶
Quantities (stocks, crypto units) follow the same pattern:
value / 10^decimal_places is the human-readable quantity. decimal_places is always 6 in MVP per N-DAT-004.
4.5 Date and Timestamp Representation¶
| Concept | JSON shape | Example |
|---|---|---|
| Logical date (transaction date) | ISO 8601 date string | "2026-04-25" |
| Timestamp | ISO 8601 datetime in UTC, with Z suffix |
"2026-04-25T14:30:00Z" |
| Timezone (family or user) | IANA name | "Europe/Kyiv" |
4.6 Enum Representation¶
Enums are JSON strings, lowercase, snake_case. Match the Postgres enum values verbatim:
5. Pagination¶
Cursor-based, opaque cursors, forward-only.
5.1 Request¶
limitdefaults to 50, max 200.cursoris omitted on the first request.- The cursor encodes both the sort key and a tiebreaker (typically the last item's ID).
5.2 Response¶
When has_more is false, next_cursor is null and the client stops paginating.
5.3 Stability¶
Cursors are stable across page fetches as long as the underlying sort order is the same. Adding new items to the front of a list does not break in-progress pagination (you'll just miss the new items in this scan).
6. Idempotency¶
Per N-API-004, mutation endpoints accept an Idempotency-Key header.
6.1 Mechanics¶
- Client generates a unique key per logical operation (UUIDv7 recommended) and includes it on the first request.
- Server stores
(user_id, idempotency_key) → (request_fingerprint, response_status, response_body)for 24 hours. - Repeat request with the same key:
- Same fingerprint → returns the cached response (status, body, headers) without re-executing.
- Different fingerprint → returns
409 Conflictwith codeIDEMPOTENCY_KEY_REUSE. (The same key cannot be used for a different request.)
6.2 Storage¶
- Hot-path: Redis with 24h TTL.
- Durable backup:
shared.idempotency_keytable (see Data Model §10.3) for survival across Redis restarts. - If Redis is unavailable, the durability table is consulted on the slow path; if both are unavailable, idempotency is skipped (degraded mode).
6.3 When To Use¶
- Required: all
POSTendpoints that create resources or trigger state changes. - Recommended: all
PATCH,PUT,DELETEendpoints (combines with optimistic locking for safety). - Not used:
GET(already idempotent),/health/*.
6.4 Client Guidance¶
A client should generate one idempotency key per logical user action (e.g., one "Save" button click), and reuse the same key on retry after network errors. Do not generate a new key on retry.
7. Optimistic Concurrency¶
Per N-API-005, mutable shared entities have a version integer. Updates must include the expected current version.
7.1 Reading Version¶
GET responses include the entity's version:
7.2 Writing With Version¶
Two equivalent ways to send the version:
(a) If-Match header (preferred):
PATCH /v1/families/{familyId}/categories/{categoryId}
If-Match: 7
Content-Type: application/json
{ "name": "Groceries & Household" }
(b) Body field:
PATCH /v1/families/{familyId}/categories/{categoryId}
Content-Type: application/json
{ "name": "Groceries & Household", "version": 7 }
Endpoints accept either; if both are present, they must agree (else 400 BAD_REQUEST with code VERSION_HEADER_BODY_MISMATCH).
7.3 Conflict Response¶
On version mismatch, server returns:
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": {
"code": "VERSION_MISMATCH",
"message": "This resource was modified by another action. Please refresh and try again.",
"request_id": "01HZ..."
},
"current": {
"id": "01H...",
"name": "Food",
"version": 8,
...
}
}
The current field contains the full current state, so the client can present a merge UI without an extra round-trip.
7.4 When Version Is Not Required¶
POST(creating; nothing to conflict with).DELETEof an already-deleted entity (returns204 No Content, idempotent).
For all other mutations on versioned entities, the version is required; missing it returns 400 with code VERSION_REQUIRED.
8. Errors¶
8.1 Error Response Shape¶
All errors return a JSON body with this shape:
{
"error": {
"code": "TRANSACTION_AMOUNT_INVALID",
"message": "Amount must be non-zero.",
"details": {
"field": "amount.minor",
"value": 0
},
"request_id": "01HZ..."
}
}
code— stable string identifier; clients map to localized messages.message— human-readable English, suitable for showing to a developer/operator. Not for end-users.details— optional, structured, error-code-specific.request_id— matches theX-Request-Idheader and structured log entry.
8.2 HTTP Status Mapping¶
| Status | Meaning | Example codes |
|---|---|---|
400 Bad Request |
Malformed request, validation failure | INVALID_ID_FORMAT, UNKNOWN_FIELD, MISSING_REQUIRED_FIELD |
401 Unauthorized |
Missing or invalid auth | INVALID_TOKEN, EXPIRED_TOKEN, MISSING_AUTH |
403 Forbidden |
Authenticated but not permitted | INSUFFICIENT_ROLE, NOT_FAMILY_MEMBER |
404 Not Found |
Resource does not exist (or caller cannot see it) | RESOURCE_NOT_FOUND |
409 Conflict |
Conflict with current state | VERSION_MISMATCH, IDEMPOTENCY_KEY_REUSE, DUPLICATE_RESOURCE |
422 Unprocessable Entity |
Business rule violation | CASH_BALANCE_NEGATIVE, CREDIT_LIMIT_EXCEEDED, INVALID_TRANSFER_PAIR |
429 Too Many Requests |
Rate limit exceeded | RATE_LIMIT_EXCEEDED |
500 Internal Server Error |
Unhandled server error | INTERNAL_ERROR (always with a request_id for support) |
503 Service Unavailable |
Dependency unavailable | DATABASE_UNAVAILABLE, MAINTENANCE_MODE |
400 is for requests we couldn't even process (syntactic / structural). 422 is for requests we processed but rejected for business reasons. Keep them distinct.
8.3 Error Code Conventions¶
- Codes are
SCREAMING_SNAKE_CASE. - Codes are stable: once shipped, they are not renamed (only deprecated and supplemented).
- New codes are added freely.
- Codes for related errors share a prefix when meaningful:
VERSION_MISMATCH,VERSION_REQUIRED,VERSION_HEADER_BODY_MISMATCH.
8.4 Validation Errors¶
For multi-field validation failures, details.errors is an array:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Request validation failed.",
"details": {
"errors": [
{ "field": "name", "code": "REQUIRED" },
{ "field": "currency", "code": "INVALID_ENUM", "allowed": ["USD", "EUR", "UAH", ...] }
]
},
"request_id": "01HZ..."
}
}
8.5 No Stack Traces in Responses¶
Production responses never include stack traces. Stack traces go to structured logs and Sentry. The request_id is the bridge between a user complaint and the server-side details.
9. Authentication¶
9.1 User Authentication¶
Login:
HTTP/1.1 200 OK
Content-Type: application/json
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "<opaque>"
}
Authenticated request:
Refresh:
Returns a new access + refresh token pair. The old refresh token is revoked.
Logout:
Revokes the current refresh token.
9.2 Google OAuth¶
Redirects to Google. After consent, Google redirects to /v1/auth/oauth/google/callback?code=.... The server exchanges the code, finds or creates the user, and returns the same login response shape.
9.3 Telegram-Linked Login¶
The Telegram bot acts as an external service (see §9.4). To link a Telegram account to an app account, the user issues a one-time link command in the bot, providing a code obtained from the web UI:
The bot, when receiving a /link ABC123 command, calls a writer endpoint that consumes the code and creates the identity.telegram_link row. After linking, bot commands resolve telegram_chat_id → user_id automatically.
9.4 External-Service Authentication¶
External services use API keys:
Per R-EXT-003, X-Acting-User-Id is required for any operation that requires a user context. The key holder is fully trusted to supply the correct user ID.
API keys cannot use endpoints that fundamentally require an interactive user session: token refresh, password changes, recovery code regeneration, account deletion, OAuth flows. Those return 403 with code API_KEY_NOT_PERMITTED.
9.5 Operator Authentication¶
The token is configured via the OPERATOR_TOKEN environment variable. The operator credential is not stored in the database. POST /v1/operator/session exchanges that token for a short-lived HttpOnly cookie. Protected operator health/admin endpoints check that cookie; X-Operator-Token is the bootstrap credential, not the steady-state browser credential. Per R-OPS-003, operator endpoints have no access to user data.
10. Authorization¶
10.1 Role Requirements per Endpoint¶
Each endpoint declares the minimum role required. The auth middleware checks the caller's role in the path's {familyId} and rejects with 403 INSUFFICIENT_ROLE if not met.
| Operation | Minimum Role |
|---|---|
| Read anything | viewer |
| Create transactions, accounts, categories, tags | editor |
| Update / delete entries the caller created | editor |
| Update / delete entries another user created | owner |
| Manage members, roles, family settings | owner |
| Delete the family | owner (and the family must not be private) |
10.2 The "Editor Deletes Own" Rule¶
Per R-FAM-010, an editor can only delete entries they themselves created. Implementation:
- The auth middleware permits the request based on role (editor passes).
- The use case checks
entity.created_by_user_id === acting_user_id. If false and the caller is not an owner, returns403 EDITOR_CANNOT_DELETE_OTHERS.
The same rule applies to updates that effectively replace authorship (none in MVP, but the principle is consistent).
10.3 Cross-Family Access¶
A user requesting {familyId} they are not a member of receives 404 RESOURCE_NOT_FOUND — never 403. (We do not leak the existence of families the caller cannot see.)
11. Rate Limiting¶
Per N-API-008.
11.1 Default Limits¶
| Caller Type | Default Limit |
|---|---|
| Authenticated user (per JWT subject) | 120 requests / minute |
| External service (per API key) | configurable, default 600 / minute |
| Unauthenticated (login attempts, public endpoints) | 30 / minute per IP |
11.2 Headers¶
Every rate-limited response includes:
Where Reset is seconds until the limit fully resets.
11.3 Exceeded¶
HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/json
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Try again in 42 seconds.",
"request_id": "01HZ..."
}
}
11.4 Configuration¶
Limits can be overridden per environment via env vars:
RATE_LIMIT_PER_USER_PER_MINUTERATE_LIMIT_PER_API_KEY_PER_MINUTE_DEFAULTRATE_LIMIT_UNAUTHENTICATED_PER_MINUTE
Per-API-key overrides live in identity.api_key.rate_limit_per_minute.
12. OpenAPI¶
Per N-API-007.
12.1 Spec Endpoint¶
Returns the full OpenAPI 3.1 specification for the v1 API. No authentication required.
12.2 Generation¶
The spec is auto-generated from request/response schemas defined in code (Zod or equivalent). The generation runs at build time and at server startup; the served spec always matches the running code.
12.3 SDK Generation¶
The web client and CLI consume the spec to generate typed clients via standard tooling. External-service developers can do the same.
12.4 Schema Stability¶
Within a major version (v1), the OpenAPI schema only grows. Field removal, type changes, and enum value removal are breaking changes reserved for v2.
13. Endpoint Catalogue¶
A non-exhaustive overview of MVP endpoints, grouped by resource. This is reference, not contract — the OpenAPI spec is canonical.
13.1 Auth¶
POST /v1/auth/register
POST /v1/auth/login
POST /v1/auth/refresh
POST /v1/auth/logout
POST /v1/auth/password-reset/initiate (uses recovery code)
POST /v1/auth/password-reset/complete
GET /v1/auth/oauth/google/start
GET /v1/auth/oauth/google/callback
13.2 Self¶
GET /v1/me
PATCH /v1/me
DELETE /v1/me (per R-FAM-016 cascade rules)
GET /v1/me/sessions
DELETE /v1/me/sessions/{sessionId}
POST /v1/me/recovery-codes/regenerate
POST /v1/me/telegram-link
DELETE /v1/me/telegram-link
GET /v1/me/families
GET /v1/me/notifications
POST /v1/me/notifications/{notificationId}/handle
GET /v1/me/export (GDPR: full owned-data export per R-GDP-001)
13.3 Families¶
POST /v1/families
GET /v1/families/{familyId}
PATCH /v1/families/{familyId}
DELETE /v1/families/{familyId}
GET /v1/families/{familyId}/members
POST /v1/families/{familyId}/members
PATCH /v1/families/{familyId}/members/{userId} (change role; owner only)
DELETE /v1/families/{familyId}/members/{userId} (remove member; or self-leave)
POST /v1/families/{familyId}/transfer-ownership (D)
GET /v1/families/{familyId}/invitations
POST /v1/families/{familyId}/invitations
POST /v1/families/{familyId}/invitations/{invitationId}/revoke
POST /v1/invitations/{invitationId}/accept (caller is the invitee)
POST /v1/invitations/{invitationId}/reject
POST /v1/invitations/redeem-link (claim a shareable invite)
GET /v1/families/{familyId}/audit (audit log per R-AUD-003)
GET /v1/families/{familyId}/export (GDPR: family export per R-GDP-001)
13.4 Accounts¶
GET /v1/families/{familyId}/accounts
POST /v1/families/{familyId}/accounts
GET /v1/families/{familyId}/accounts/{accountId}
PATCH /v1/families/{familyId}/accounts/{accountId}
DELETE /v1/families/{familyId}/accounts/{accountId}
GET /v1/families/{familyId}/accounts/{accountId}/balance
13.5 Transactions (regular activity)¶
These endpoints handle non-investment transactions (income, expense, transfer). They map to ledger.transaction. For investment-account activity, see §13.7.
GET /v1/families/{familyId}/transactions
POST /v1/families/{familyId}/transactions
POST /v1/families/{familyId}/transactions/transfer (creates the linked pair; both sides in ledger.transaction)
GET /v1/families/{familyId}/transactions/{transactionId}
PATCH /v1/families/{familyId}/transactions/{transactionId}
DELETE /v1/families/{familyId}/transactions/{transactionId}
POST /v1/families/{familyId}/transactions/{transactionId}/clear (pending → cleared)
The target account for POST and POST /transfer must be a non-investment account; otherwise the API returns 422 with code INVALID_ACCOUNT_TYPE_FOR_TRANSACTION. For investment-account activity, see §13.7 Investment.
13.6 Categories & Tags¶
GET /v1/families/{familyId}/categories
POST /v1/families/{familyId}/categories
PATCH /v1/families/{familyId}/categories/{categoryId}
DELETE /v1/families/{familyId}/categories/{categoryId}
GET /v1/families/{familyId}/tags
POST /v1/families/{familyId}/tags
DELETE /v1/families/{familyId}/tags/{tagId}
13.7 Investment¶
Investment-account activity is exposed under its own resource path so clients can be built specifically for the investment surface (per Architecture §3.4).
GET /v1/symbols (instance-shared + caller's user-defined)
POST /v1/symbols (creates user-defined; or canonical via external_id)
GET /v1/symbols/{symbolId}
GET /v1/symbols/{symbolId}/prices
POST /v1/symbols/{symbolId}/prices (manual price record)
GET /v1/families/{familyId}/accounts/{accountId}/positions
GET /v1/families/{familyId}/accounts/{accountId}/positions/{positionId}
GET /v1/families/{familyId}/investment-transactions
POST /v1/families/{familyId}/investment-transactions
GET /v1/families/{familyId}/investment-transactions/{transactionId}
PATCH /v1/families/{familyId}/investment-transactions/{transactionId}
DELETE /v1/families/{familyId}/investment-transactions/{transactionId}
POST /v1/families/{familyId}/investment-transactions/{transactionId}/clear
Cross-account transfers between regular and investment accounts (e.g., depositing cash from a debit account into a brokerage) use a dedicated endpoint that owns the cross-table linking:
This endpoint accepts a payload describing both sides and creates the transfer_group plus one row in each transaction table inside a single DB transaction. The use case lives in the Investment context (per Architecture §3.4).
The target account for an investment-transactions POST/PATCH must be an investment account; otherwise the API returns 422 with code INVALID_ACCOUNT_TYPE_FOR_TRANSACTION.
13.8 Currency¶
GET /v1/currencies
GET /v1/currencies/{code}
GET /v1/fx-rates (?from=USD&to=UAH&date=2026-04-25)
POST /v1/fx-rates (manual rate record)
13.9 Imports¶
GET /v1/families/{familyId}/imports
POST /v1/families/{familyId}/imports (multipart: file + metadata)
GET /v1/families/{familyId}/imports/{importJobId}
DELETE /v1/families/{familyId}/imports/{importJobId} (cancel queued or remove completed)
13.10 Reports¶
GET /v1/families/{familyId}/reports/balances
GET /v1/families/{familyId}/reports/spend-by-category (?period=...&granularity=...)
GET /v1/families/{familyId}/reports/income-vs-expense
GET /v1/families/{familyId}/reports/net-worth-over-time
GET /v1/families/{familyId}/reports/portfolio
13.11 Operator¶
GET /v1/operator/health/info
GET /v1/operator/users
POST /v1/operator/users/{userId}/disable
POST /v1/operator/users/{userId}/enable
GET /v1/operator/api-keys
POST /v1/operator/api-keys
PATCH /v1/operator/api-keys/{apiKeyId}
DELETE /v1/operator/api-keys/{apiKeyId}
13.12 Public¶
14. Common Patterns¶
14.1 Listing With Filters¶
Standard query parameters supported on transaction lists (and analogously on others where applicable):
| Parameter | Type | Example |
|---|---|---|
from |
date | 2026-01-01 |
to |
date | 2026-04-25 |
account_id |
uuid (repeatable) | account_id=...&account_id=... |
category_id |
uuid (repeatable) | |
tag_id |
uuid (repeatable) | |
kind |
enum (repeatable) | kind=expense&kind=income |
min_amount_minor |
integer | |
max_amount_minor |
integer | |
currency |
char(3) | |
text |
string | Substring match against note. |
cursor |
string | |
limit |
integer | |
sort |
string | date.desc (default), date.asc, amount.desc, amount.asc |
Multiple values for the same key combine with OR; different keys combine with AND. Per R-RPT-005 (MVP is AND-only across keys; OR within a key is implicit through repetition).
14.2 Soft-Delete Endpoints¶
DELETE performs a soft-delete (sets deleted_at). To list including soft-deleted rows (e.g., for audit views):
This is restricted to owner role.
To restore a soft-deleted entity:
Restore is owner-only and returns 404 if the entity is not soft-deleted (avoids ambiguity).
14.3 Bulk Operations¶
Not supported in MVP. Each operation is one HTTP request. Justification: simpler API; idempotency and optimistic locking work cleanly per-resource; the import flow handles the bulk-data use case via a different mechanism (jobs).
14.4 File Uploads¶
For CSV imports:
POST /v1/families/{familyId}/imports
Content-Type: multipart/form-data
--boundary
Content-Disposition: form-data; name="metadata"
Content-Type: application/json
{ "account_id": "01H...", "importer": "csv-app-format" }
--boundary
Content-Disposition: form-data; name="file"; filename="statement.csv"
Content-Type: text/csv
(file bytes)
--boundary--
Returns the created import_job immediately (status queued). Client polls for completion.
14.5 Long-Running Operations¶
For operations that take more than a few seconds (imports, exports, future LLM processing): create a job, return its ID, client polls. Never block an HTTP request waiting for slow work.
15. Versioning Within a Resource¶
When a request's body would change the resource's version, the response includes the new version:
Clients update their local copy with the response's version before any subsequent edit.
16. Webhooks¶
Not in MVP. Reserved as a future extension point. When implemented, webhook subscriptions will be a distinct concept from external-service API keys, with their own delivery, retry, and signature mechanics.
17. Where to Go Next¶
- The entities behind these endpoints: Data Model
- The architectural rationale: Architecture
- What to implement and when: Requirements and Roadmap
- Conventions for agentic implementers: Agents