Agent Memory
API Reference

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_...
CredentialUse 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 tokenMCP 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

Conventions

  • JSON bodies, camelCase field 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, agentId and userId, 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.

CodeStatusMeaning
VALIDATION_FAILED400 / 413 / 422Input rejected: unknown memory type, bad enum value, oversized body, collection cap, blank content.
RECEIPT_MISMATCH400Feedback named a receipt that doesn't belong to this run, or used IDs it didn't deliver. Extensions: receiptId, undelivered.
UNAUTHORIZED401Missing or invalid credentials.
FORBIDDEN403Authenticated but not allowed (capability, tenant mismatch, tenant-scope content).
policy_denied403The memory policy refused an action on a memory the caller can see. Extensions: action, rule.
TENANT_SUSPENDED403The caller's own tenant is suspended or pending deletion. Extension: tenantStatus.
API_ACCESS_DISABLED403API access is switched off for this tenant by the operator.
ENTITLEMENT_REQUIRED403The plan does not include this feature.
NOT_FOUND404Absent, or hidden from this principal.
IDEMPOTENCY_CONFLICT409Idempotency-Key reused with a different request, or the first request is still in flight.
deletion_blocked_by_active_dependents409Live memories derive from the target. Extension: blockingDependents. Retry with cascade=true.
concurrent_update409Another request changed the memory first; re-read and retry.
precondition_failed412If-Match named a stale version. Extension: currentVersion.
CONTEXT_BUDGET_TOO_SMALL422The token budget can't hold non-negotiable policy. Extensions: requestedTokens, requiredTokens.
RATE_LIMITED429The request-rate limiter rejected the call. Retry-After set.
QUOTA_EXCEEDED429A plan quota (for example monthly operations) is exhausted. Retry-After, resource, limit, current.
UPSTREAM_UNAVAILABLE503The LLM or embedding provider is throttling or unavailable. Retry-After may be set.
OVERLOADED503The API is at its concurrency ceiling. Retry-After: 5.
TENANT_MAINTENANCE503The tenant is in operator read-only mode. Retry-After: 300.
PLACEMENT_UNAVAILABLE503The 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 204 again rather than 404.
  • 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_LIMITED with Retry-After: 60.
  • Plan quotas — such as monthly metered operations on the memory endpoints — answer 429 QUOTA_EXCEEDED with Retry-After and the resource, limit and current values.

Treat every 429 and 503 as retryable after Retry-After, with backoff.

Request limits

Out-of-range sizes are clamped, not rejected:

InputRangeDefault
topN (retrieve)1–100server default
tokenBudget (retrieve)256–32,000workspace setting (4,000)
limit (episode and procedure lists)1–10010
topK (episode and file search)1–505

Hard caps (exceeding them is an error):

InputMax
Request body1 MiB by default (413)
files on POST /v1/files/document50 entries (422)
cases on POST /v1/memory/evals/run200 (400)
limit on the change feed500 (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.

On this page