Skip to content

HTTP API

The API contract

Everything the client does is a plain HTTP call you can make yourself. This page is the contract: how you authenticate, what comes back, what the failures mean, and which endpoint does what.

Base URL

Every path below is relative to this origin. Treat the host as a variable — it will move to a custom domain — and keep it in one place in your configuration rather than spread through your code.

base URL
https://pfmem-api.packagefactory.dk

Two ways to authenticate

A bearer API key

Everything else. The key resolves server-side to an account, a workspace, a role and a set of verbs. Never put it in a query string, in an error report, or in anything that records a URL.

request header
authorization: Bearer mk_live_...

A session cookie

Used by the sign-up and sign-in routes, and by the two account routes — the one that mints an account's first master key, and the one that hands a browser its CSRF token. This is the browser's mechanism, and it is the only way to reach the endpoint that issues the first key, because before that call there is no key to authenticate with.

The CSRF token

A state-changing request authenticated by a cookie must echo this session's token in a header, because a browser attaches a cookie to a cross-site request on its own. A caller using an API key needs none of this: a browser never attaches a key by itself, so a key cannot be used cross-site.

session-authenticated writes
GET  /api/v2/account/csrf-token   → { "csrf_token": "...", "header_name": "x-csrf-token" }
POST /api/v2/account/master-key   → x-csrf-token: <that token>

The envelope

Every endpoint answers in one of two shapes. Success carries a request id and a data object; failure carries an error with a stable code, a human message and a type.

success
{
  "request_id": "23ba77f0c6474ccb8dc5d65d1a44d069",
  "data": { }
}
failure
{
  "error": {
    "code": "version_conflict",
    "message": "…",
    "type": "invalid_request_error"
  }
}

The API never returns 200 on an error. If the status is 200 there is a data object, and if there is an error object the status says so too.

Branch on the code, never on the message. Never show a raw code to a person — map it to copy they can act on.

Put the request id somewhere copyable on any error screen. It is the first thing support will ask for, and the service logs the same value.

Status codes

Five codes cover every failure. Only two of them are worth retrying: a retry on the other three sends the same rejected request again.

Status codes, their meaning and how to handle them
CodeMeaningHow to handle it
401Missing or invalid key.Ask for a key, or sign in again. Never retry.
403The key lacks the verb, or the target is out of its scope.Name the missing permission. Never retry.
422The request failed validation.Show the error against the field it belongs to. Never retry.
429A quota or rate limit was reached.Show the limit and when it resets. Retry after backing off.
503Something MemoryAgent depends on is unavailable. The request was not processed; retry it.Transient. Retry with backoff.

Key tiers

The prefix tells you what a key can reach before you use it. A list of keys never returns a secret — only a prefix, a label and a date — because a secret is shown once, at the moment it is minted.

Key prefixes, tiers, scope and verbs
PrefixTierScopeVerbs
mk_live_masterEvery workspace in the account.read · write · delete
tk_live_tenantOne workspace.read · write · delete
rk_live_tenant_roleOne workspace, one role.A reader may read; a writer may read and write.

Memory

The surface your application calls, authenticated by a bearer key. Ingestion is asynchronous, so a memory that is still being processed is a real state in your interface rather than an edge case.

Memory endpoints
EndpointPurpose
POST /api/v2/memory/addStore a conversation. Returns a task id; extraction runs behind it.
POST /api/v2/memory/searchRanks results by combining meaning-based and exact-wording matches.
POST /api/v2/memory/getList a subject's memories, filtered and paginated.
POST /api/v2/memory/editAmend one stored memory. It is re-indexed, so later searches match the new wording.
POST /api/v2/memory/deleteDelete extracted memories. The underlying conversation is retained.
POST /api/v2/memory/flushRun a session's pending extraction now.

Tasks

The only two GETs in the memory API. Statuses are queued, running, succeeded, failed and cancelled; the last three are terminal, so stop polling when you reach one.

Task endpoints
EndpointPurpose
GET /api/v2/tasksList background jobs.
GET /api/v2/tasks/{task_id}One job's status and progress.

Administration

Master key only. This is where workspaces and keys are managed, and it is deliberately outside the command-line client's surface.

Administration endpoints
EndpointPurpose
POST /api/v2/admin/tenantsCreate a workspace.
POST /api/v2/admin/tenants/listList the account's workspaces.
POST /api/v2/admin/tenants/suspendSuspend a workspace. Reversible.
POST /api/v2/admin/tenants/resumeResume a suspended workspace.
POST /api/v2/admin/tenants/eraseErase a workspace. Irreversible — everything in it is removed outright.
POST /api/v2/admin/keysMint a key.
POST /api/v2/admin/keys/listList keys. Never returns a secret.
POST /api/v2/admin/keys/revokeRevoke a key.
POST /api/v2/admin/keys/rotateRotate a key and issue its replacement.
POST /api/v2/admin/credentialsStore your own provider key, encrypted.

The release channel

Five public routes with no authentication, serving the installer and the published artifacts. They are absent from the OpenAPI document on purpose: that document is the memory contract your application calls, and a shell script and a download are infrastructure.

Public release endpoints
EndpointReturns
GET /install.shThe installer, stamped with the origin it was fetched from.
GET /cli/latestThe published version, its sha256 and its size.
GET /cli/download/:versionThe client bundle.
GET /cli/skill/latestThe published skill version, its sha256 and its size.
GET /cli/skill/download/:versionThe skill body.

Conventions worth knowing before you write a client

Nearly everything is POST.
Reads included. A read takes a body of filters, and a key belongs in a header rather than in a URL that proxies and browser history will record. The two task endpoints are the only GETs. Do not treat this as an oversight to correct.
Timestamps are unix milliseconds.
Thirteen digits, at or above 1000000000000, in both directions. A seconds-precision value is rejected rather than read as a date in 1970.
A read names exactly one subject.
The listing and the search each take one subject identifier, and it must match the memory type: a user query names a user, an agent query names an agent. Sending both always fails validation, so build the choice as a toggle rather than two fields.
Bodies are validated strictly.
An unrecognised field is a validation error, not something quietly ignored. A misspelt name fails loudly instead of appearing to work — and no field you could send names a workspace, a filter or an embedding version in the first place.