Skip to content

Install and configure

One client, four calls: identify, track, batch and emails.send.

Terminal window
npm install @cowliss/sdk
import { Cow } from "@cowliss/sdk";
const cow = new Cow({
apiKey: process.env.COW_KEY!,
sourceId: "src_01j2x8q7v9e3atn5m4kd7yz0bp",
baseUrl: process.env.COW_API_URL,
});
Option Default Meaning
apiKey required Your org’s API key, the Bearer credential on every request. One key serves every app.
sourceId none The source calls arrive through when they do not name one. Set it once per app.
baseUrl local dev The origin your Cowliss API is served at, with no trailing slash.
maxRetries 2 Retries after the first attempt, on network errors and 5xx only.
timeoutMs 10000 How long one call may take before it is abandoned and retried.
batchTimeoutMs 30000 The same for one batch, which carries up to 500 rows and is slower by design.
onError none Called with the CowError of any failed call. This is where a call you do not await gets logged.
fetch global An injectable fetch, for tests and runtimes that do not have one.
Id Looks like You use it for
Source id src_01j2x8q7… Every call. It goes in the client config and on every HTTP body.
App id app_my-app Segment definitions, journey triggers, filtering the event feed.

You send the source; you filter on the app. A process writing into two apps names sourceId per call, or holds one client per app. See Apps and sources.

// You need the result: await it, and handle the error.
const profile = await cow.identify({ identifiers: { userId: "user_1" } });
// You do not: let it run, and keep serving the request.
cow.track({ identifiers: { userId: "user_1" }, event: "checkout_started" });

A call you do not wait for never rejects into your process. If Cowliss is unreachable it gives up on its own, within timeoutMs per attempt, and reports through onError:

const cow = new Cow({
apiKey: process.env.COW_KEY!,
sourceId: "src_01j2x8q7v9e3atn5m4kd7yz0bp",
onError: (error) => logger.warn({ err: error }, "cowliss write dropped"),
});

Wait when the next thing you do depends on the write: a profile you are about to read back, or a segment you expect the event to move.

A 4xx surfaces as a typed CowError carrying the API’s error code, and is never retried. Network errors, timeouts and 5xx retry with exponential backoff.

import { CowError } from "@cowliss/sdk";
try {
await cow.track({ identifiers: { userId: "user_1" }, event: "checkout_started" });
} catch (error) {
if (error instanceof CowError && error.code === "over_quota") {
// out of credit: the write did not land
}
}

An unknown sourceId, an archived source, a reserved event name and a future timestamp are all refused rather than quietly dropped. Every code and its status is in API conventions.

Requests and responses use the { data } envelope, and the endpoints are documented in the API reference.

Terminal window
curl -X POST $COW_API_URL/v1/identify \
-H "Authorization: Bearer $COW_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"sourceId": "src_01j2x8q7v9e3atn5m4kd7yz0bp",
"identifiers": { "userId": "user_1" },
"traits": { "email": "[email protected]", "plan": "pro" }
}
}'

A raw client sends its own messageId on a track.