# Install and configure

> Part of the Cowliss docs. Markdown for every page: append `index.md` to its URL. Site map: /llms.txt

One client, five calls: [`identify`](/sdk/identify/), [`track`](/sdk/track/), [`batch`](/sdk/batch/), [`emails.send`](/sdk/emails/) and [`preferences.link`](/sdk/emails/#a-link-to-their-email-preferences).

```sh
npm install @cowliss/sdk
```

```ts
import { Cow } from "@cowliss/sdk";

const cow = new Cow({
  apiKey: process.env.COW_KEY!,
  sourceId: process.env.COW_SOURCE_ID!,
  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` | `https://api.cowliss.com` | 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. |

## The two ids you hold

| 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, automation 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](/tracking/apps/).

## Calling without waiting

```ts
// 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`:

```ts
const cow = new Cow({
  apiKey: process.env.COW_KEY!,
  sourceId: process.env.COW_SOURCE_ID!,
  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.

## Errors

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.

`error.code` is the API's code and `error.status` the HTTP status it came with. `status` is `0` when no answer arrived (`network_error`), or when the client refused the call before sending it, such as one with no `sourceId`.

```ts
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 inactive 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](/api-conventions/).

A retry never writes or sends twice: `track` and `emails.send` carry an idempotency key on every attempt, and `identify` matches on its identifiers. A retried send whose first attempt went through answers with that send's `deliveryId`.

## Versions

The package's `CHANGELOG.md` lists every version after 0.16.0; the [Changelog](/changelog/) page is the CLI's. Before that, 0.15.0 added the `source_inactive` and `version_outdated` error codes and 0.15.1 made `baseUrl` default to `https://api.cowliss.com`; 0.14.0 and 0.16.0 changed nothing in the SDK.

## Without the SDK

Requests and responses use the `{ data }` envelope, and the endpoints are documented in the [API reference](/reference/api/ingestion/).

```sh
curl -X POST $COW_API_URL/v1/identify \
  -H "Authorization: Bearer $COW_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "sourceId": "'"$COW_SOURCE_ID"'",
      "identifiers": { "userId": "user_1" },
      "traits": { "email": "ada@acme-demo.io", "plan": "pro" }
    }
  }'
```

A raw client sends its own `messageId` on a track.
