Skip to content
Cowliss is in beta: features and pricing may change.

batch

Two things make a backfill safe: historical timestamps on any write, and one request carrying up to 500 calls.

await cow.batch({
items: [
{ type: "identify", identifiers: { userId: "user_1" }, traits: { plan: "pro" } },
{
type: "track",
identifiers: { userId: "user_1" },
event: "subscription_started",
timestamp: "2026-03-01T00:00:00.000Z",
messageId: "stripe-evt_0001",
},
],
});

One batch carries one source, so it never spans apps.

identify and track both accept timestamp. On a track it becomes the event’s own time (receivedAt still records when Cowliss saw the call). On an identify a first-write profile is backdated; an existing profile keeps its original createdAt.

A timestamp in the future is refused (with 60 seconds of clock-skew tolerance), and so is one more than 18 months old. Anything in between imports whatever your retention window (90 days unless you set another). An event older than the window never shows in the event feed or exports, but segments still count it, so importing last year’s orders arms the lapses that follow them. See the settings reference.

{
"data": {
"results": [
{ "ok": true, "type": "identify", "id": "usr_…" },
{ "ok": true, "type": "track", "id": "evt_…", "deduped": false },
{ "ok": false, "error": { "code": "trait_validation_failed", "message": "…" } }
]
}
}

One bad row never fails the batch: it reports the error the single call would have, and the rest land. Request-level problems (malformed JSON, over 500 items, a bad envelope) are still 400 or 422 for the whole request.

Batch writes land in storage and start no automation: neither their raw events nor the segment entries they cause, so importing two years of history never mails long-gone users. Segment membership does recompute, because segments tell the truth, and the import arms what follows it: a window that lapses after the import, such as a customer’s 120th day without an order, starts that segment’s automation on the day it lapses.

People who were already in a segment when you import, or when you build it, enter an automation when somebody turns the automation on, with the count in front of them.

Rule of thumb: live writes and lapsing windows start automations; imports and new segments tell the truth and start nobody.

Give every track item a stable messageId from the source system (stripe-evt_0001, not a random UUID). A crashed import is then simply restarted: replays dedupe per item ("deduped": true) instead of double-writing. The request-level Idempotency-Key header does not apply to batch items, because one shared key would collapse them onto the first.

Backfilled events are metered like any other event you wrote. Events Cowliss records itself never are. See Billing.