API overview
Base URL, authentication, versioning, errors, idempotency, ETags, rate limits and request limits for the Agent Memory REST API.
The REST API is the contract every other surface is built on: the SDKs and MCP tools call the same endpoints with the same semantics.
Base URL and versioning
All data-plane endpoints live under /v1:
https://memory.empecta.com/v1/... # the hosted service
https://<your-deployment>/v1/... # a self-hosted deployment
http://localhost:5199/v1/... # a typical local self-hosted instance/v1 covers memory, blocks, user profiles, episodes, procedures, files, categories and the learning loop. Additive changes (new fields, new endpoints) ship in place; breaking changes ship under a new prefix (/v2) with a deprecation window.
The unversioned /api aliases are deprecated
The historical /api/... paths (for example /api/memory/append) still serve the same endpoints with identical shapes, but every response carries Deprecation: true, a Sunset date (default Wed, 30 Jun 2027 00:00:00 GMT) and a Link: </v1/...>; rel="successor-version" header. Replace the /api prefix with /v1.
The machine-readable OpenAPI document is served at GET /openapi/v1.json.
Authentication
Every /v1 request needs a bearer token:
Authorization: Bearer amk_...| Credential | Use it for |
|---|---|
Workspace API key (amk_…) | Servers, SDKs, CI, MCP clients that accept a header. A key authorizes exactly one workspace and carries capabilities and an agent binding. |
| OAuth 2.1 access token | MCP clients that run the OAuth flow (Claude, VS Code) and delegated user access, issued by the built-in authorization server. |
Every request also names its workspaceId (in the body for POST/PUT, in the query or path otherwise). A workspace the credential does not hold answers 404, not 403, so its existence does not leak. Capabilities and agent binding are explained in Identity and visibility; key management in Authentication.
Resource groups
Memory
Memory governance
Learning loop
Blocks and profiles
Episodes and procedures
Files and categories
Provisioning
Conventions
- JSON bodies,
camelCasefield names. Enum values are strings and case-insensitive on input ("Workspace","workspace"). - Timestamps are ISO 8601 / RFC 3339. Offsets are accepted and normalized to UTC.
- IDs are GUIDs, except
runId,agentIdanduserId, which are your own strings. - Lists returned by retrieve are ordered best match first.
Errors
Error bodies use application/problem+json:
{
"type": "https://agentmemory.dev/errors/VALIDATION_FAILED",
"title": "VALIDATION_FAILED",
"status": 400,
"detail": "The request was rejected by domain validation. See traceId for details.",
"code": "VALIDATION_FAILED",
"traceId": "0af7651916cd43dd8448eb211c80319c"
}Branch on status and code, never on detail. detail is a stable, generic sentence; specifics go to the server log under the same traceId — include it in support requests. Some errors add extension members (listed below). A plain 404 for a missing or hidden resource, and some 401/403 responses from the authentication layer, have an empty body.
| Code | Status | Meaning |
|---|---|---|
VALIDATION_FAILED | 400 / 413 / 422 | Input rejected: unknown memory type, bad enum value, oversized body, collection cap, blank content. |
RECEIPT_MISMATCH | 400 | Feedback named a receipt that doesn't belong to this run, or used IDs it didn't deliver. Extensions: receiptId, undelivered. |
UNAUTHORIZED | 401 | Missing or invalid credentials. |
FORBIDDEN | 403 | Authenticated but not allowed (capability, tenant mismatch, tenant-scope content). |
policy_denied | 403 | The memory policy refused an action on a memory the caller can see. Extensions: action, rule. |
TENANT_SUSPENDED | 403 | The caller's own tenant is suspended or pending deletion. Extension: tenantStatus. |
API_ACCESS_DISABLED | 403 | API access is switched off for this tenant by the operator. |
ENTITLEMENT_REQUIRED | 403 | The plan does not include this feature. |
NOT_FOUND | 404 | Absent, or hidden from this principal. |
IDEMPOTENCY_CONFLICT | 409 | Idempotency-Key reused with a different request, or the first request is still in flight. |
deletion_blocked_by_active_dependents | 409 | Live memories derive from the target. Extension: blockingDependents. Retry with cascade=true. |
concurrent_update | 409 | Another request changed the memory first; re-read and retry. |
precondition_failed | 412 | If-Match named a stale version. Extension: currentVersion. |
CONTEXT_BUDGET_TOO_SMALL | 422 | The token budget can't hold non-negotiable policy. Extensions: requestedTokens, requiredTokens. |
RATE_LIMITED | 429 | The request-rate limiter rejected the call. Retry-After set. |
QUOTA_EXCEEDED | 429 | A plan quota (for example monthly operations) is exhausted. Retry-After, resource, limit, current. |
UPSTREAM_UNAVAILABLE | 503 | The LLM or embedding provider is throttling or unavailable. Retry-After may be set. |
OVERLOADED | 503 | The API is at its concurrency ceiling. Retry-After: 5. |
TENANT_MAINTENANCE | 503 | The tenant is in operator read-only mode. Retry-After: 300. |
PLACEMENT_UNAVAILABLE | 503 | The tenant's dedicated database is not ready. Retry-After: 60. |
Idempotency
Memory mutations accept an optional Idempotency-Key header (1–128 characters; UUIDs recommended):
POST /v1/memory/append · PUT /v1/memory/{id} · DELETE /v1/memory/{id} · POST /v1/memory/{id}/share · POST /v1/memory/consolidate
- The first successful (2xx) response is stored for 24 hours, per tenant — status included, so a replayed delete answers
204again rather than404. - Retrying with the same key and the same request (path, query and body) returns the stored response with
Idempotency-Replayed: true. The mutation does not run twice. - The same key with a different request — another body, another memory ID, another workspace — or while the first request is still in flight answers
409 IDEMPOTENCY_CONFLICT. - Failed responses are not stored, so a failed request can be retried with the same key.
- A key longer than 128 characters is rejected with
422.
Without the header, behaviour is unchanged. Append's semantic de-duplication (near-duplicate merging) applies independently.
ETags and conditional updates
PUT /v1/memory/{id} supports optimistic concurrency. Read the memory's version, then send it back:
If-Match: "7""7", W/"7" and 7 are all accepted; * or no header means no precondition. A stale version answers 412 precondition_failed with currentVersion and nothing is written. A successful update returns the new version in both the body and the ETag header. See Updating and forgetting.
Rate limits and quotas
- Request rate is limited per tenant in fixed one-minute windows. Anonymous traffic gets a tighter per-IP window, and an optional per-workspace fairness window can apply to API-key traffic. The limits are deployment configuration (see Configuration).
- A rejected request answers
429 RATE_LIMITEDwithRetry-After: 60. - Plan quotas — such as monthly metered operations on the memory endpoints — answer
429 QUOTA_EXCEEDEDwithRetry-Afterand theresource,limitandcurrentvalues.
Treat every 429 and 503 as retryable after Retry-After, with backoff.
Request limits
Out-of-range sizes are clamped, not rejected:
| Input | Range | Default |
|---|---|---|
topN (retrieve) | 1–100 | server default |
tokenBudget (retrieve) | 256–32,000 | workspace setting (4,000) |
limit (episode and procedure lists) | 1–100 | 10 |
topK (episode and file search) | 1–50 | 5 |
Hard caps (exceeding them is an error):
| Input | Max |
|---|---|
| Request body | 1 MiB by default (413) |
files on POST /v1/files/document | 50 entries (422) |
cases on POST /v1/memory/evals/run | 200 (400) |
limit on the change feed | 500 (400) |
Health
GET /api/health and GET /api/version are unauthenticated and outside /v1. Self-hosted deployments also expose /health/live and /health/ready — see Operations.