The Server-side HTTP API: Decide, Events and Config

A reference for the authenticated /sdk endpoints, for a backend without the Pertento SDK: deciding variants, reporting conversions and reading the setup.

3 min read


For a backend in a language without a Pertento SDK, the same decisions and conversions are available over HTTP. Every endpoint is on https://tracking.pertento.ai, takes a server key as Authorization: Bearer psk_live_…, and is rate limited per key. See Server keys: creating, rotating and revoking.

POST /sdk/decide

Decides on Pertento's side. 600 requests a minute per key.

curl https://tracking.pertento.ai/sdk/decide \
  -H "Authorization: Bearer $PERTENTO_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "keys": ["new-checkout"], "visitorId": "3f2504e0-4f89-41d3-9a0c-0305e82c3301", "attributes": { "plan": "pro" } }'

The body:

  • keys: the flag keys to decide, at most 50. Leave it out to decide every experiment.
  • visitorId: the visitor ID, a UUID.
  • id: your customer ID, at most 256 characters, for an experiment that buckets on a customer ID.
  • attributes: at most 50 entries and 4 KB, matched against the experiment's attribute rules.

The response:

{
  "visitorId": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
  "visitorIdMinted": false,
  "decisions": { "new-checkout": { "experimentId": 501, "variantId": 9002, "variantKey": "one-page", "payload": { "layout": "single" }, "enrolled": true } },
  "seed": { "visitorId": "3f2504e0-4f89-41d3-9a0c-0305e82c3301", "assignments": { "501": { "variantId": "9002" } } }
}

A decision that is not enrolled names control and carries a reason. The reasons are listed in Installing the Pertento server SDK.

Store a new visitor ID

When you send no ID at all, Pertento makes a new visitor ID and returns it with visitorIdMinted: true. Store it, in your session or a first-party cookie you set, and send it back as visitorId on every later call for that person. Pertento sets no cookies and cannot see the browser's ID from here, so a caller that does not send it back turns every request into a new visitor.

Write the seed into the page

Put the response's seed in the page's head as window.PertentoSeed, before the Pertento script. Escape <, >, &, U+2028 and U+2029 as \u003c, \u003e, \u0026, \u2028 and \u2029, so no value can close the script element:

<script>window.PertentoSeed={"visitorId":"3f2504e0-4f89-41d3-9a0c-0305e82c3301","assignments":{"501":{"variantId":"9002"}}};</script>

POST /sdk/events

Reports conversions, and needs the website's tracking method set to Server-side. 600 requests a minute per key, a budget separate from /sdk/decide, so a burst of decisions cannot crowd out conversions.

curl https://tracking.pertento.ai/sdk/events \
  -H "Authorization: Bearer $PERTENTO_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "events": [{ "name": "purchase", "data": { "revenue": 49.99, "currency": "EUR" } }], "keys": ["new-checkout"], "visitorId": "3f2504e0-4f89-41d3-9a0c-0305e82c3301", "attributes": { "plan": "pro" } }'

The body:

  • events: 1 to 20, each { "name", "data" }, with a name of at most 100 characters and data of at most 8 KB.
  • keys: required, 1 to 50 flag keys, the ones this person was decided on.
  • visitorId, id or both: one is required.
  • attributes: optional, and the same ones sent to /sdk/decide.

Unknown fields are refused. The answer is { "ok": true, "experimentVariantMap": { "501": "9002" } }: the experiments the conversion was credited to. Send each conversion once and never retry it. See Reporting conversions from your server.

GET /sdk/config

What the SDK downloads: the website's experiments, variants, weights, payloads and attribute rules, and the salt they are hashed with. 120 requests a minute per key. Send If-None-Match with the last ETag to get an empty 304 when nothing has changed.

The response contains the website's secret salt, so treat it like the key. The rules an SDK in another language has to follow, with test vectors, are in the spec folder of the SDK repository.

Errors

Errors answer { "error": "message" } with one of these statuses:

  • 400: the body is not valid.
  • 401: the key is not valid. A key created moments ago can be refused for up to about 30 seconds, so retry with backoff for a short while before treating it as wrong.
  • 404: the website has no server-side setup.
  • 409: the website's tracking method is not Server-side.
  • 429: too many requests.
  • 503: the tracking server is starting.

On 429 and 503, wait as long as the Retry-After header says when it is present.