Skip to Content

SDK Quickstart

Before you begin

New to CompetLab? Create a free account  and set up your first project first — the calls below read your projects, so you need one to get data back.

You need Node.js 20 or newer and a CompetLab API key. Create the key in the dashboard under Settings → API Keys — it starts with cl_live_ and is 40 characters. Copy it when it’s shown; you won’t be able to read it again. (More in Authentication.)

Install

npm install @competlab/sdk

Zero runtime dependencies, ESM and CommonJS both supported.

Construct a client

The API key is the one required option. Keep it in an environment variable and pass it in — the SDK doesn’t read the environment for you:

import CompetLab from '@competlab/sdk'; const cl = new CompetLab({ apiKey: process.env.COMPETLAB_API_KEY! });

The ! is there because process.env values are typed as possibly undefined while apiKey is a required string. In production you’d check the variable is set and fail loudly if it isn’t, rather than assert it.

List your projects

Most methods take a projectId, and cl.projects.list() is where you get one:

const { data } = await cl.projects.list(); for (const project of data.items) { console.log(project.id, project.name, project.domain); }

Two things to notice, both explained below: you destructure data, and you write data.

Pull a dimension

With a projectId, read any of the six monitored dimensions. Here’s the latest pricing intelligence:

const projectId = '65a1b2c3d4e5f6a7b8c9d0e1'; const { data } = await cl.pricing.dashboard(projectId); console.log(data.item.summary);

Swap pricing for aiVisibility, aiSources, positioning, content, or techTrust to read the other dimensions. A single dimension resource comes back as { item }; a list comes back as { items }. The aiVisibility, aiSources and techTrust reads answer in a compact view unless you pass view: 'full': each long list inside them is one page — see Long lists come a page at a time.

For the synthesized read across all of them, call cl.strategicBriefing.get(projectId) — but its envelope carries more than { item }. It returns { meta, item, coverage, contains, tickets }, and item is null unless the latest run finished, so branch on meta.status before reading it:

const { data } = await cl.strategicBriefing.get(projectId); if (data.meta.status === 'done') { console.log(data.item); }

meta.status is 'running' | 'done' | 'failed' | null. Only null means the project has never had a briefing — on running or failed, an earlier edition is usually still readable via cl.strategicBriefing.history().

What the edition recommends isn’t in item. Every move in the briefing lands on your Strategic Tickets board — as a new ticket, most important first, or on the ticket already there for that work. data.tickets says what the edition did to the board, and cl.tickets.list(projectId, { origin: 'briefing', briefingRunId: data.meta.runId! }) reads the tickets it opened.

The projectId above is illustrative. Real IDs are 24-character hex strings — use one from your own cl.projects.list() response.

Why data

Every method returns an object with the parsed data plus the raw request and response, so you pull out data. It is always present: the client throws on any non-2xx response, so a call that returns has succeeded. No ! assertion, and no if (result.error) branch — that branch was declared but never produced, and it’s gone as of v3.

const { data } = await cl.projects.list(); console.log(data.items.length);

Handle failures with try/catch, as the next step shows.

Handle errors

Any non-2xx response throws a typed CompetLabError. Catch it and branch on status or code:

import CompetLab, { CompetLabError } from '@competlab/sdk'; try { const { data } = await cl.projects.get('does-not-exist'); console.log(data.item.name); } catch (err) { if (err instanceof CompetLabError) { console.error(`${err.status} ${err.code}: ${err.message}`); // 404 project_not_found: Project not found } else { throw err; } }

code is a snake_case string matching the REST API’s error codes — api_key_invalid, project_not_found, and so on. A failure that never reached the API arrives as the same class with code: "network_error", and a gateway or proxy answering in the API’s place gives code: "http_error".

Run a free scan (start, then poll)

Three of the free tools — tech stack, trust signals, and agent adoption — are asynchronous: you start a live scan of a URL you provide, then poll for the result. They don’t need a projectId — just your key:

// Start the scan — you get back a scan ID const { data } = await cl.tools.techStack.startScan({ domain: 'example.com' }); const scanId = data.item.id; // Poll until its status is `completed` or `failed` — typically 30–90 seconds let result; const deadline = Date.now() + 3 * 60_000; // bound the wait, as with any polling loop while (Date.now() < deadline) { const scan = (await cl.tools.techStack.getScan(scanId)).data.item; if (scan.status === 'completed') { result = scan.result; break; } if (scan.status === 'failed') throw new Error(scan.error?.code); await new Promise((r) => setTimeout(r, 5000)); // the recommended interval is 5–10 seconds } console.log(result);

The same start/get pattern applies to cl.tools.trustSignals and cl.tools.agentAdoption. The other three tools — cl.tools.sitemapVisualizer, cl.tools.aiCrawlerChecker, and cl.tools.fetchUrl — are synchronous: they return their result directly in one call, no scan ID and no polling. Scan IDs expire after 24 hours.

Next steps

  • SDK reference → — all 54 methods, grouped by resource, with parameters, return types, and the endpoint each maps to.
  • REST API → — the HTTP surface the SDK wraps, if you want to see what’s underneath.
Last updated on