POST /v1/events
Tell Sortance what a signup went on to do. Experiments are scored on these events, segments are cut by them, and recommendations are drawn from them. Without them, every report on this API is empty, and says so.
Request
curl -X POST https://api.sortance.com/v1/events \
-H "Authorization: Bearer $SORTANCE_KEY" \
-H "Content-Type: application/json" \
-d '{ "trace_id": "dc_01J...", "event": "activated", "at": 1759400000, "properties": { "plan": "pro" } }'
// or up to 100 at once, all-or-nothing:
{ "events": [ { "trace_id": "...", "event": "subscribed", "at": 1759400000 } ] }
// 202
{ "accepted": 1, "ids": ["ev_01J..."] }| Field | Type | Meaning |
|---|---|---|
trace_id | string | Required. The trace_id /v1/decide (dc_…) or /v1/verify (vr_…) returned for this person. |
event | string | Required. One of the names below. |
at | number | Required. Epoch SECONDS when it happened. More than a day in the future is rejected — the usual cause is milliseconds. |
properties | object | Optional and flat: up to 20 keys matching ^[a-z][a-z0-9_]{0,63}$, values string (up to 256 characters), number or boolean, at most 2,048 bytes in total. |
- Never send an address. A property value that looks like an email address is rejected with a
400that says which key, without echoing the value. - A batch is all-or-nothing: one invalid event fails the request with its index, so you never have to work out which half landed.
- The
trace_idmust be one of your own. One that does not exist, or belongs to another account, is still accepted but joined to nothing. - Events you send are not de-duplicated. A retried request is recorded again.
- Free and unmetered, with the same ceiling as /v1/outcome: 6,000 requests a minute. Billing you for telling us what happened would be billing you for making the product better.
- If you connect Stripe, payment events arrive on their own and you do not need to send
subscribed,upgraded,churned,refundedorfraud_flagyourself.
Event names
| event | Means |
|---|---|
signup_completed | They finished signing up. |
activated | They reached whatever you call activation. |
subscribed | They started paying. The default metric for segments. |
upgraded | They moved to a higher plan. |
churned | They left. |
refunded | You refunded them. |
support_ticket | They opened a ticket. Useful as an experiment guardrail. |
fraud_flag | You (or a dispute) flagged them. |
custom:<name> | Anything else. <name> is lowercase letters, digits and _, starting with a letter, up to 64 characters. |
What events feed: GET /v1/segments
Outcome rates by the things Sortance already knows about every signup. Query metric (default subscribed) and window_days (default 90, 1–365).
{
"window_days": 90,
"metric": "subscribed",
"join": { "exact": 0, "by_email": 0, "unmatched": 0 },
"segments": [ { "key": { "mailbox_type": "consumer" }, "exposures": 0, "events": 0, "rate": 0, "ci": [0, 0] } ],
"too_few": [ { "key": { "actor_class": "agent_mailbox" }, "exposures": 0 } ],
"computed_at": 1759400000
}- Dimensions:
mailbox_type,esp,actor_class,signals.disposable,signals.role_account,company.industry,company.employees_range,company.country,person.seniority,icp.fit,decision,rule_id,attributes.<name>. - Computed nightly and served from storage, never from raw decisions on request.
computed_atsays when;nullmeans the nightly job has not run for your account yet. - A segment with fewer than 30 exposures is listed under
too_fewwith no rate. Rates are posterior means with a 90% interval. joincounts how each event was tied to a decision: exactly by trace id, approximately by email (Stripe only), or not at all. The numbers carry their own caveat.
GET /v1/recommendations
Deterministic suggestions drawn from the same nightly numbers: a segment with at least 100 exposures whose rate is below your account’s overall rate with at least 95% probability gets a proposed rule, with the evidence attached.
- The proposed action is
review, neverdeny. A deny recommendation would be Sortance deciding who gets in, and only your rules do that.proposed_action_alternativeslists the stricter options for you to choose. - Each one carries
evidence(segment, rate, baseline, probability, exposures, events), acost_note, andtry_as_experimentdefaults: weight 20, min_sample 400. - The
sayssentence is a template filled from the numbers. No model writes it. At most 10 are returned.
Errors
400 invalid_request with a message naming the field (and the index, in a batch); 401 for a bad key; 429 above the rate. Request bodies are capped at 256 KB. /v1/segments and /v1/recommendations return 400 for a metric that is not an event name or a window_days outside 1–365.