Skip to content

Decide a batch of features (browser)

POST
/edgekit/v1/decide
curl --request POST \
--url https://your-tenant-host/edgekit/v1/decide \
--header 'Content-Type: application/json' \
--header 'x-edgekit-site-id: site_example' \
--data '{ "features": [ "paywall", "newsletter-prompt" ], "content": { "payload": "{\"article_id\":\"a-1\",\"section\":\"news\",\"paywall_level\":\"2\"}", "signature": "example" }, "context": { "additionalProperty": "example" }, "userId": "0199a4c2-7b1e-7c3d-9a4f-2e8b5d6c1a90", "giftLink": "example" }'

One request carries the facts once and a list of feature names. Every feature evaluates against one immutable fact snapshot, and effects are deduped and applied after every feature has resolved — so a batch is order-independent and replayable from the request alone. Rate limited to 2,000 requests per 10 seconds per API key on /v1/decide, or 200 per 10 seconds per client per site on /edgekit/v1/decide — one flat window, no separate burst. Exceeding it returns 429 with Retry-After. A retry is not free: every call ticks the meters its rules charge (a seen-set meter counts one article once, a count meter every call).

x-edgekit-site-id
required

The site this request is for, within this tenant. Every official EdgeKit SDK sets it from the client you configured; set it yourself only when calling this API directly.

string
>= 1 characters <= 128 characters

Example

site_example

The site this request is for, within this tenant. Every official EdgeKit SDK sets it from the client you configured; set it yourself only when calling this API directly.

x-edgekit-preview

A short-lived preview token for this site. When valid, the decision evaluates against the staged preview document instead of the live one, and the response’s preview field is true. An absent, expired or invalid token falls back to the live document, with no error.

string

A short-lived preview token for this site. When valid, the decision evaluates against the staged preview document instead of the live one, and the response’s preview field is true. An absent, expired or invalid token falls back to the live document, with no error.

Media typeapplication/json
object
features
required

The features to evaluate, named by the caller. A feature enabled in the console does not start evaluating — or ticking meters — because somebody saved a draft.

Array<string>
>= 1 items <= 32 items

Example

[
"paywall",
"newsletter-prompt"
]
content

The signed content descriptor. Absent means the decision reads no content facts, and a feature that reads one then routes to its error slot — which may not allow.

object
payload
required

The content descriptor, serialised. Signed as these exact bytes — there is no canonicalisation step, because a canonicalising scheme is a second parser that has to agree with the first.

string
>= 1 characters

Example

{"article_id":"a-1","section":"news","paywall_level":"2"}
signature
required

Base64url HMAC-SHA256 of payload under this site’s content key.

string
>= 1 characters
context

Unverified passthrough. Never merged with the signed facts, and a rule that allows on one of these is a bypass the organization wrote itself.

object
key
additional properties
userId

Who this decision is for. Accepted on the keyed route only, and refused outright (403) on /edgekit/v1/decide — a site-pinned key is a server credential and asserting an identity is what one is for, while a browser asserting one would let anyone on the page decide as any user. On the browser route the identity comes from the signed access cookie and nowhere else.

string format: uuid
>= 1 characters

Example

0199a4c2-7b1e-7c3d-9a4f-2e8b5d6c1a90
giftLink

The page’s ?_gt gift-link token, verbatim. Verified here with no read; an invalid, forged, expired or overlong token reads as no gift and the answer is still 200.

string

One entry per feature that resolved.

Media typeapplication/json
object
decisions
required
object
key
additional properties
object
outcome
required

The id the site switches on.

string | null
value

The winning outcome’s own named fields, flattened to plain JSON — never a stringified blob. Absent for a plain outcome, where the id is the whole answer. A ref field resolves to a curated catalog object (never a bare id) carrying exactly the keys its select named.

type

Present iff the matched outcome declared one. allow/deny is an access verdict; redirect names a destination (see GET /decide/forward); data is neither. Open: accept a member not listed.

string
reason
required
string
reasonDetail
string
meters

The meter reads this feature’s rules made, keyed by meter name, as read — before this request’s own ticks. allowance - count is what was left going in. Absent when no rule read a meter.

object
key
additional properties
object
count
required
integer
allowance
required
integer
unknown
required

Names that were asked for and not answered — unpublished, disabled, or not running on this site. Reported rather than thrown: one typo must not fail the other five in the batch.

Array<string>
preview
required

Whether this answer came from the preview document rather than the live one.

boolean
staleIdentity

Present and true when this request carried a session cookie but no usable signed access cookie, so the answer above was decided with no grants. Call GET /v1/id/get-session to refresh the access cookie, at most once per decide: the flag only checks that a session cookie is present and never verifies it, so refetching without a limit on a forged cookie never ends. Absent means nothing is wrong.

boolean
exposures
required

This batch’s deduped, edgekit-owned experiment exposures — one per experiment, not per feature, and never a vendor-assigned one (the vendor’s own SDK already recorded that). Collection stays client-side: fire each of these as a trackSelfDescribingEvent against iglu:cloud.edgekit/experiment_exposure/jsonschema/1-0-0, tagged preview from this same response’s preview field if you need to exclude it at analysis time — nothing here writes it for you.

Array<object>
object
experiment
required
string
arm
required
string

Example

{
"decisions": {
"additionalProperty": {
"type": "allow"
}
}
}

Invalid request.

Media typeapplication/json
object
error
required
object
code
required
string
message
required
string
details

Example

{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key."
}
}

Not allowed.

Media typeapplication/json
object
error
required
object
code
required
string
message
required
string
details

Example

{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key."
}
}

Resource not found.

Media typeapplication/json
object
error
required
object
code
required
string
message
required
string
details

Example

{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key."
}
}

Request body too large.

Media typeapplication/json
object
error
required
object
code
required
string
message
required
string
details

Example

{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key."
}
}

Request validation failed.

Media typeapplication/json
object
error
required
object
code
required
string
message
required
string
details

Example

{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key."
}
}

Too many requests. Retry-After says when to retry.

Media typeapplication/json
object
error
required
object
code
required
string
message
required
string
details

Example

{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key."
}
}

Unexpected error.

Media typeapplication/json
object
error
required
object
code
required
string
message
required
string
details

Example

{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key."
}
}

With Retry-After, briefly unavailable; without it, not configured.

Media typeapplication/json
object
error
required
object
code
required
string
message
required
string
details

Example

{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key."
}
}