Quickstart
From an empty account to a memory you can search
Seven steps, about five minutes, and one of them is irreversible. Step two shows a secret exactly once — read it before you run it.
Create an account
Sign up with an email, a password and a name. The address has to be confirmed before the account can be used, so this call returns no session — it sends a link instead. Open the link, then sign in; that second call is what sets the cookie the next step needs.
POST /api/auth/sign-up/emailcurl -fsS -X POST https://pfmem-api.packagefactory.dk/api/auth/sign-up/email \ -H 'content-type: application/json' \ -c cookies.txt \ -d '{"email":"you@example.com","password":"a-long-passphrase","name":"Your Name"}'Until the address is confirmed, sign-in answers 403 with EMAIL_NOT_VERIFIED. The link is valid for one hour, and the page it lands on can send another.
POST /api/auth/sign-in/emailcurl -fsS -X POST https://pfmem-api.packagefactory.dk/api/auth/sign-in/email \ -H 'content-type: application/json' \ -c cookies.txt \ -d '{"email":"you@example.com","password":"a-long-passphrase"}'You can do this in the browser instead. The result is the same session cookie. Signing up with an address that already exists gets the same answer as a new one — the response is deliberately not a way to ask which addresses are registered.
Mint the master key
This one call creates the account record and issues its first master key. It authenticates with the session cookie rather than a bearer key, and — because a cookie rides along on its own — it must echo this session's CSRF token in a header. Read the token first, then mint.
GET /api/v2/account/csrf-tokencurl -fsS https://pfmem-api.packagefactory.dk/api/v2/account/csrf-token \ -b cookies.txtPOST /api/v2/account/master-keycurl -fsS -X POST https://pfmem-api.packagefactory.dk/api/v2/account/master-key \ -H 'content-type: application/json' \ -H 'x-csrf-token: <the token from the call above>' \ -b cookies.txt \ -d '{"label":"bootstrap master key","account_name":"Acme"}'The secret is shown exactly once
The response carries the key's secret. It is hashed server-side the moment it is issued and cannot be retrieved again, by us or by you. Save it in a password manager before you close the terminal.200 OK{ "request_id": "23ba77f0c6474ccb8dc5d65d1a44d069", "data": { "key": { "key_id": "key_01HQ", "tier": "master", "label": "bootstrap master key", "prefix": "mk_live_", "tenant_slug": null, "role": null, "verbs": ["read", "write", "delete"], "created_at": 1755640000000 }, "secret": "mk_live_<shown here and nowhere else, ever>" } }Calling this a second time is refused, because an account holds one first master key and there is nothing to re-issue. If the secret is gone, the way back is rotation, not a retry.
Create a workspace
A workspace is a boundary around one set of memories — one per customer, per environment, or per project. Creating one gives it a private space of its own, with nothing else in it and nothing shared with any other workspace. The slug is yours to choose within the allowed shape; the internal name it resolves to is derived from it and returned to you, never sent.
POST /api/v2/admin/tenantscurl -fsS -X POST https://pfmem-api.packagefactory.dk/api/v2/admin/tenants \ -H 'authorization: Bearer mk_live_...' \ -H 'content-type: application/json' \ -d '{"slug":"acme_prod","display_name":"Acme (production)", "active_embedding_version":"external_v1"}'Authenticate with the master key as a bearer token from here on. A slug is lowercase letters, digits and underscores, up to forty characters — a hyphen is a validation error, not a slug with a hyphen in it. The embedding version names an entry that already exists in the registry; it is not a label you invent per workspace.
Mint a key scoped to that workspace
Master keys are for administration. Day-to-day traffic should carry the narrowest credential that still works, so mint a workspace-scoped key and give that to the client. The tier and the slug in the body below are what narrow it.
POST /api/v2/admin/keyscurl -fsS -X POST https://pfmem-api.packagefactory.dk/api/v2/admin/keys \ -H 'authorization: Bearer mk_live_...' \ -H 'content-type: application/json' \ -d '{"tier":"tenant","label":"acme_prod client","tenant_slug":"acme_prod"}'The request body is validated strictly: an unrecognised field is a validation error rather than a silently ignored one, so a misspelt name fails loudly instead of widening nothing.
Install the client and store the key
Pipe the key in on standard input rather than passing it as an argument. An argument shows up in the process list for every user on the machine and is written to your shell history; standard input is neither.
pfmem initcurl -fsSL https://pfmem-api.packagefactory.dk/install.sh | sh export PATH="$HOME/.pfmem/bin:$PATH" cat key.txt | pfmem initThe stored config file is created with owner-only permissions, and the key it holds is only ever sent to an https origin or to loopback.
Store a conversation
Ingestion is asynchronous. The call returns as soon as the messages are durable and hands back a task id; extraction into searchable memories happens behind it. Nothing is searchable the instant this returns, which is what the next step is for.
pfmem addpfmem add --mode chat --user-id u_42 --session-id s_1 \ --idempotency-key batch-2026-08-18-01 \ --messages '[{"role":"user","content":"I moved to Berlin"}]'The idempotency key makes a retry after a timeout safe: a replay returns the first result instead of ingesting the same batch twice.
Read it back
Force the pending extraction rather than waiting for the background pipeline, then ask a question. The flush blocks for as long as you allow it; the search matches your question against what the workspace holds — by meaning as well as by exact wording — and returns a ranked set of facts, not a transcript.
pfmem flush · pfmem searchpfmem flush --session-id s_1 --wait-ms 10000 pfmem search --memory-type user --user-id u_42 --query "where does he live now?"Use the ranked search when you have a question. Use the plain listing when you want everything held about a subject, in a stable order, with real pagination.
pfmem getpfmem get --memory-type user --user-id u_42 --limit 50
Where to go from here
The command reference covers every flag, the exit codes and the log file. The API reference covers the envelope, the status codes and the endpoints these commands call.