Skip to main content
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 problem documents, including the request id.
Create a key on the API keys page and pass it as a bearer token. These snippets are ESM (.ts / .mts) and run on Node 22+ without a transport library.

Types and client

client.ts
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.
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.
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`);
Treat next_cursor as opaque — pass back the exact value you received. See Pagination for the full contract.

Track an account

POST /accounts requires platform (instagram or tiktok) and username.
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):
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.
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.
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