> ## Documentation Index
> Fetch the complete documentation index at: https://docs.creatoraudit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript

> A typed CreatorAudit API client using the built-in fetch (Node 22+).

This page shows a small, typed CreatorAudit API client built on the global
`fetch` available in Node 22+ — no dependencies. It sets the bearer header and
base URL once, exposes an async generator that follows cursor pagination, and
throws a typed error from [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)
problem documents, including the request id.

<Info>
  Create a key on the [API keys page](https://app.creatoraudit.com/app/api-keys) and
  pass it as a bearer token. These snippets are ESM (`.ts` / `.mts`) and run on Node 22+
  without a transport library.
</Info>

## Types and client

```typescript client.ts theme={null}
const BASE_URL = "https://api.creatoraudit.com/v2";

// Standard cursor-pagination envelope returned by list endpoints.
export interface Page<T> {
  data: T[];
  pagination: {
    next_cursor: string | null;
    has_next: boolean;
    limit: number;
    total_count?: number | null;
  };
}

// Single-resource envelope returned by reads and writes.
export interface Single<T> {
  data: T;
}

// RFC 9457 problem document (application/problem+json).
export interface ProblemDetail {
  type: string;
  title: string;
  status: number;
  detail: string;
  code: string;
  instance?: string | null;
}

export class CreatorAuditError extends Error {
  readonly status: number;
  readonly code: string;
  readonly detail: string;
  readonly type: string;
  readonly requestId: string | null;

  constructor(problem: ProblemDetail, requestId: string | null) {
    super(`[${problem.status} ${problem.code}] ${problem.title}: ${problem.detail}`);
    this.name = "CreatorAuditError";
    this.status = problem.status;
    this.code = problem.code;
    this.detail = problem.detail;
    this.type = problem.type;
    this.requestId = requestId;
  }
}

export class CreatorAudit {
  constructor(
    private readonly apiKey: string,
    private readonly baseUrl: string = BASE_URL,
  ) {}

  async request<T>(
    method: string,
    path: string,
    init: { query?: Record<string, string | number>; body?: unknown } = {},
  ): Promise<T> {
    const url = new URL(this.baseUrl + path);
    for (const [key, value] of Object.entries(init.query ?? {})) {
      url.searchParams.set(key, String(value));
    }

    const response = await fetch(url, {
      method,
      headers: {
        Authorization: `Bearer ${this.apiKey}`,
        ...(init.body ? { "Content-Type": "application/json" } : {}),
      },
      body: init.body ? JSON.stringify(init.body) : undefined,
    });

    if (!response.ok) {
      const requestId = response.headers.get("X-Request-ID");
      // Errors are application/problem+json; degrade gracefully otherwise.
      const problem = (await response
        .json()
        .catch(() => ({}))) as Partial<ProblemDetail>;
      throw new CreatorAuditError(
        {
          type: problem.type ?? "about:blank",
          title: problem.title ?? response.statusText,
          status: problem.status ?? response.status,
          detail: problem.detail ?? "",
          code: problem.code ?? "INTERNAL_ERROR",
        },
        requestId,
      );
    }

    return (await response.json()) as T;
  }
}
```

## Identify your key

`GET /whoami` confirms the key works and returns its organization.

```typescript theme={null}
interface WhoAmI {
  type: string;
  organization_id: string;
  api_key_name: string | null;
}

const client = new CreatorAudit(process.env.CREATORAUDIT_API_KEY!);

const { data: me } = await client.request<Single<WhoAmI>>("GET", "/whoami");
console.log(me.organization_id, me.api_key_name);
```

## Paginate any list endpoint

This async generator follows the cursor: it passes the previous response's
`next_cursor` back as `cursor` and stops once `has_next` is `false`.

```typescript theme={null}
async function* paginate<T>(
  client: CreatorAudit,
  path: string,
  query: Record<string, string | number> = {},
  limit = 200,
): AsyncGenerator<T> {
  let cursor: string | null = null;

  do {
    const page: Page<T> = await client.request<Page<T>>("GET", path, {
      query: { ...query, limit, ...(cursor ? { cursor } : {}) },
    });

    yield* page.data;
    cursor = page.pagination.has_next ? page.pagination.next_cursor : null;
  } while (cursor !== null);
}

// Walk every tracked TikTok account:
interface Account {
  id: string;
  platform: string;
  username: string;
}

let count = 0;
for await (const account of paginate<Account>(client, "/accounts", {
  platform: "tiktok",
})) {
  count += 1;
}
console.log(`${count} accounts`);
```

<Note>
  Treat `next_cursor` as opaque — pass back the exact value you received. See
  [Pagination](/api-reference/pagination) for the full contract.
</Note>

## Track an account

`POST /accounts` requires `platform` (`instagram` or `tiktok`) and `username`.

```typescript theme={null}
const { data: created } = await client.request<Single<Account>>("POST", "/accounts", {
  body: {
    platform: "tiktok",
    username: "creatoraudit",
    category: "tech",
    scrape_interval_hours: 24,
  },
});
console.log("tracking", created.id);
```

To track a single video, `POST /videos` takes `platform` and an `identifier`
(a full URL, a numeric id, or — for Instagram — a bare shortcode):

```typescript theme={null}
const { data: video } = await client.request<Single<{ id: string }>>(
  "POST",
  "/videos",
  {
    body: {
      platform: "tiktok",
      identifier: "https://www.tiktok.com/@creatoraudit/video/712345",
      is_active: true,
    },
  },
);
console.log("tracking video", video.id);
```

## Organization overview

`GET /overview` accepts a `period` of `7d`, `30d`, or `90d`.

```typescript theme={null}
const { data: overview } = await client.request<Single<Record<string, unknown>>>(
  "GET",
  "/overview",
  { query: { period: "30d" } },
);
console.log(overview.current, overview.changes);
```

## Handling errors

Any non-2xx response throws `CreatorAuditError` with the parsed problem fields
and the `X-Request-ID`, so you can branch on `code` and report the request id.

```typescript theme={null}
try {
  // missing username — triggers a validation error
  await client.request("POST", "/accounts", { body: { platform: "tiktok" } });
} catch (err) {
  if (err instanceof CreatorAuditError) {
    if (err.code === "VALIDATION_ERROR") {
      console.error("fix the request body:", err.detail);
    }
    console.error("status:", err.status, "request-id:", err.requestId);
  } else {
    throw err;
  }
}
```

## Next steps

* [Introduction](/api-reference/introduction) — auth, base URL, and conventions
* [Pagination](/api-reference/pagination) — the cursor contract in detail
* [Quickstart](/quickstart) — your first request end to end
