Recommendations with receipts
Send your catalog and events, get a ranked list with a reason for every slot, and a holdout that proves what it earned.
“More about magnesium” · more_about_tag 0.82
How it works
Send items, send events, ask for a slate. Every call is JSON over HTTPS with a scoped key. The SDKs add timeouts, a fallback and a consent check.
export PATTERNA_INGEST_KEY="pt_live_..." # from Connectors, shown onceexport PATTERNA_SERVE_KEY="pt_live_..."Upsert what can be recommended, up to 500 per call. Patterna keeps the id, the kind and a few attributes; body text only becomes tags.
PUT/v1/catalog/itemscurl -X PUT https://api.patterna.nl/v1/catalog/items \ -H "Authorization: Bearer $PATTERNA_INGEST_KEY" \ -H "Content-Type: application/json" \ -d '{"items": [{ "external_id": "doc_1842", "kind": "document", "collection": "recipes", "title": "Havermout met magnesiumrijke zaden", "locale": "nl", "tags": [{ "tag": "magnesium", "weight": 1 }] }]}'{ "upserted": 1, "enrichment_queued": 1}Views, completions, joins: up to 500 events per call, idempotent on your own event id. An email or a name in the payload rejects the call.
POST/v1/eventscurl -X POST https://api.patterna.nl/v1/events \ -H "Authorization: Bearer $PATTERNA_INGEST_KEY" \ -H "Content-Type: application/json" \ -d '{"events": [{ "event_id": "item_viewed:doc_1842:u_mfrggzdfmztwq2lknnwg23tpobyxe", "type": "item_viewed", "occurred_at": "2026-10-13T09:30:00Z", "user_id": "u_mfrggzdfmztwq2lknnwg23tpobyxe", "source": "backend", "props": { "item_id": "doc_1842", "collection": "recipes" } }]}'{ "accepted": 1, "duplicates": 0, "rejected": []}Name the place in your app and the consent mode. You get item ids in order, a reason per slot and a request id to report taps against.
POST/v1/recommendationscurl -X POST https://api.patterna.nl/v1/recommendations \ -H "Authorization: Bearer $PATTERNA_SERVE_KEY" \ -H "Content-Type: application/json" \ -d '{ "surface": "home_articles", "mode": "personalized", "user_id": "u_mfrggzdfmztwq2lknnwg23tpobyxe", "context": { "locale": "nl", "platform": "ios" }, "slots": 6 }'{ "request_id": "req_01HZX4T9W6Q2", "arm": "treatment", "items": [{ "item_id": "doc_1842", "position": 0, "reason_key": "similar_to_read", "reason": { "en": "Because you read 3 magnesium recipes this week" }, "score": 0.82 }]}Then report what the user saw and tapped with POST /v1/interactions and the slate’s request_id. Full reference: ingest and serve.
Architecture
The SDK lives in your backend, next to your keys. Patterna is an engine, a jobs worker and this panel, all on one Postgres with row-level security. No queue, no second store to run.
Proof
The control arm is your own list. The treatment arm is the engine. Both are logged the same way, the interval is clustered on the user, and a row stays unreadable until the sample is large enough.
Consent
personalizedRanks from this user’s history.cohortRanks for a segment, never for one person.non_profiledContext only. No user id is stored, and the user still counts in the holdout.Who it is for

Get started
npm install patterna-sdk go get github.com/payam-h/patterna-sdk/go@latest pip install "git+https://github.com/payam-h/patterna-sdk#subdirectory=python" Using curl or another language? There is nothing to install: every call is JSON over HTTPS. The contract is one OpenAPI file.