Orishare
Concepts

Events and identity

Ingestion modes, idempotency, the client-event trust contract and customer identity.

POST /v1/events accepts one event or up to 100 (events: [...], 32 KB each). The client supplies id (your business id, unique per environment forever), name (dotted snake_case), customer_id or anonymous_id, occurred_at (at most 24 h in the future, see event time; older than 30 days is flagged late) and properties.

customer_id is your external id for the customer (your own user id), never Orishare's internal cust_... id; an event for an external id nobody holds creates that customer. The same value may be sent as external_customer_id or as customer: { "external_id": ... }. To address a customer by internal id, send customer: { "id": "cust_..." } instead (an unknown one is rejected with resource_not_found). A value sent as the external id that is an existing customer's internal id, while no customer has it as its external id, is refused with validation_failed on that field, so an internal id sent by mistake never creates a second customer. Any other value is an ordinary external id, whatever its spelling.

  • async (default): 202 with the stored event's evt_... id; poll GET /v1/events/{id} with that id for status. Your business id is the event's client_event_id; the poll route does not take it.
  • sync: 200 with effects and trace; falls back to 202 fallback: true if evaluation cannot finish in time. Sync is for secret keys only.
  • batch: 207 with one result per item, matched by index (accepted, evaluated, duplicate, rejected); a rejected item has an error and no id. The agent guide lists every field.

A stored id sent again answers duplicate with the original evt_... id and admits nothing: execution_id and effects are those of the original if it was already evaluated, otherwise null and []. The same id with a different payload is 409 event_id_conflict. A secret-key POST /v1/events does not read Idempotency-Key; the event id is its dedupe. Customer events (POST /v1/customer/events) require Idempotency-Key and have no duplicate status; see Customer integration.

Customer event ids are per customer. The id a customer session sends (POST /v1/customer/events, or a publishable POST /v1/events with session proof) names an event of that customer only: two customers may send the same id and both events count, and no customer can use up an id that another customer or your backend will send. Secret-key ids keep one namespace for the whole environment and are never matched against a customer's ids. After a merge, a resend of an id that any merged customer sent before answers as that earlier event, the surviving customer's own event first; earlier events are never renamed. Responses, receipt URLs, GET /v1/events and the customer export show the id as it was sent. Customer events stored before ids were per customer keep their stored id and still answer a resend from their own customer. Event ids cannot start with cs: on any route (that prefix is reserved): a new event with such an id is refused with validation_failed.

Event time and clock skew

occurred_at is when the activity happened by your clock; Orishare stamps received_at when the event arrives. Clocks drift, so a server running a little fast sends times slightly ahead of ours. The rules:

  • Up to 24 hours ahead of received_at is accepted. The event is evaluated now, at min(occurred_at, received_at): limit windows, daily caps, campaign budgets, counters, program periods (streak days, seasons, levels) and enrollments all use that clamped instant. A match stamped 00:00:21 that arrives at 23:59:50 counts toward the day that is ending, never toward a day that has not started. mode: "sync" returns its effects as for any present-time event.
  • The stored occurred_at stays exactly what you sent; GET /v1/events/{id} shows it.
  • More than 24 hours ahead is refused with 400 occurred_at_in_future (in a batch, that item is rejected with the same code and param events.<i>.occurred_at). Fix the sender's clock or the value; nothing was stored.
  • A past occurred_at is honoured for server events (late events count toward the period they belong to). Browser and app events use receipt time unless the event definition's time_policy.client.offline_seconds allows an older activity time; a device clock ahead of ours always gets receipt time, never a refusal.

An accepted event is never parked because of its time. If evaluation fails for another reason it is retried, and after five failed attempts its status becomes poison with the reason recorded. POST /v1/events/{id}/retry runs it again with its original identity against the current rules.

Event definitions and schemas

Every event name gets a definition (GET /v1/event_definitions, GET /v1/event_definitions/{id}) with the observed_schema Orishare recorded from accepted events, a few samples, and what you add with PATCH /v1/event_definitions/{id}: a description, client_allowed, a confirmed_schema and schema_enforcement.

A definition is created the first time a secret key sends a well-formed name it has never seen: such an event is accepted, not rejected, and the new definition is server-only (client_allowed: false). A misspelled name therefore becomes a new event; check names on your side or against GET /v1/event_definitions.

The confirmed schema says what the event should look like. It is a flat map of property to a type (string, number, boolean, null, array, object, timestamp), a list of types, or { "type": ..., "required": true }. Listed properties are type checked when present, extra properties are allowed. It feeds the rule builder's field list and AI drafting. By itself it rejects nothing: schema_enforcement decides that, and a new definition starts off.

schema_enforcementWhat ingestion does
offNothing. The schema is documentation only.
warnEvents that break the schema are accepted and counted: schema_violation_count and schema_last_violation (property, message, event id, time and a bounded sample) show what enforce would reject. The count starts again when the schema or the mode changes.
enforceEvents that break the schema are rejected before anything is stored: validation_failed with param events.<i>.properties.<name>. A corrected retry with the same id is accepted.

warn and enforce need a confirmed schema (validation_failed otherwise), and clearing the schema (confirmed_schema: null) sets the mode back to off. Run warn for a while before enforce. In a configuration document schema_enforcement is optional: when it is absent a new definition is created off and an existing one keeps its mode; enforce or warn without a schema is refused.

Trust contract

Publishable keys may send only events whose definition has client_allowed: true. In an activated customer environment they require a live customer session in X-Orishare-Customer-Token; identity comes only from that proof. Use /customer/events (or the session-bound /events adapter), a stable event ID and Idempotency-Key, then poll the safe receipt. No inline evaluation is promised. Valuable events such as order.paid remain server-only and return 403 event_requires_secret_key from a browser. See Customer integration for the complete paired-credential flow.

Identity

A trusted backend maps its external identity to a customer: POST /v1/customers upserts by external_id (201 created, 200 updated) and returns the internal id. An upsert advances version by one only when it carries non-empty attributes (even unchanged ones) or a timezone; with attributes omitted or {} and no timezone the version stays as it was, so always use the version the response returns. A verified provider connection or trusted backend exchange issues an opaque session; an anonymous grant remains anonymous. Customer routes reject caller-supplied identity and trust selectors. Linking an anonymous source to an authenticated target requires two proofs through the merge API. Ownership links preserve physical event, ledger and program history; source sessions are revoked. The legacy alias-only operation does not perform this full ownership merge.

Before activation, the retained legacy browser path uses anonymous_id or a server-minted HMAC customer_token. After activation those credentials fail with 410 customer_credential_retired; migrate to sessions rather than retrying old tokens. Secret-key event ingestion and v1 rules retain their behavior.

Deleting any retained member of a linked customer group tombstones and redacts the whole group, revokes its sessions and enqueues analytics redaction per physical member. Accounting and permanent ownership evidence remain for liability and replay safety.

On this page