Skip to Content
APICommon integrations

Common integrations

Four patterns cover most of what people build on the API. Each one is a shape rather than a finished script.

Nightly warehouse sync

The most common integration: land Asky data in BigQuery, Snowflake, Postgres, or wherever your reporting lives.

Schedule it after the pipeline, not at midnight. Prompts execute on a daily cycle and downstream extraction follows, so a job at 00:05 UTC reads a day that is not finished. Early morning UTC is a safer window, and pulling through yesterday rather than through today avoids partial days entirely.

Overlap your window and upsert. Late-arriving data does get written to a day after that day has closed. A non-overlapping window silently misses those records, and nothing about the response tells you it happened.

const end = new Date(Date.now() - 86_400_000) // yesterday const start = new Date(end.getTime() - 2 * 86_400_000) // two days of overlap const rows = await fetchAll(`/brands/${brandId}/visibility/timeseries`, { start_date: start.toISOString().slice(0, 10), end_date: end.toISOString().slice(0, 10), }) await warehouse.upsert('asky_visibility', rows, { onConflict: ['brand_id', 'date', 'engine'], })

Store the fields you use, not the whole object. New fields are added within a version, and a schema that mirrors the full response needs a migration every time. Selecting what you need means additive changes cost you nothing.

Sync each brand separately and record per-brand success. One brand failing should not silently abort the others, and you want to know which one to re-run.

Joining Asky data to your own

There is no way to attach your own identifiers to Asky objects, so joins use stable natural keys.

To join onUse
A brandIts domain, from GET /v1/brands
A competitorIts domain
A cited pageThe normalised URL
A promptThe prompt text, plus its topic
A topicIts name within the brand

Asky ids are stable for the lifetime of an object and are the right thing to store alongside your rows once you have matched them. Just do not try to construct or parse them: they are opaque.

Keep the mapping in a table you control, populated by matching on the natural key. If someone renames a topic, you update one mapping row rather than rebuilding a join across your warehouse.

Weekly report

Pull a fixed window and send it wherever your team reads things.

const range = { start_date: '2026-08-04', end_date: '2026-08-10' } const [visibility, competitors, citations] = await Promise.all([ get(`/brands/${brandId}/visibility`, range), get(`/brands/${brandId}/competitors/rankings`, { ...range, limit: 5 }), get(`/brands/${brandId}/citations/overview`, range), ])

Two things worth doing here. Request the previous period as well and report the change, because a number without a direction is hard to act on. And fetch the underlying data once, then derive each section locally, rather than one request per section: it is fewer requests and the sections reconcile with each other, since they came from a single read.

Alerting on a change

Compare a recent window against the one before it and alert past a threshold you choose.

const [current, previous] = await Promise.all([ get(`/brands/${brandId}/visibility`, { start_date: '2026-08-05', end_date: '2026-08-11' }), get(`/brands/${brandId}/visibility`, { start_date: '2026-07-29', end_date: '2026-08-04' }), ]) const delta = current.data.visibility_score - previous.data.visibility_score if (delta < -5) notify(`Visibility down ${Math.abs(delta).toFixed(1)} points week over week`)

Use two windows of the same length, and whole days in both. Comparing seven days against six and a half shows a drop caused by the missing half day, not by anything that happened.

Daily comparisons are noisy for this data. Week over week is usually the shortest window where a move means something.

Practical notes

One key per integration. The warehouse sync and the alerting job should hold separate keys, so usage tells you which one is misbehaving and either can be revoked alone.

Alert on the job not running. The most common silent failure is a schedule that quietly stopped. Absence of a successful run is the signal worth watching, more than errors within one.

Handle zero rows explicitly. A sync that succeeds and writes nothing usually means a filter stopped matching, often after a topic or tag was renamed. It looks identical to success in every log.

Keep concurrency low. Parallel workers share a key’s rate limit. Low single digits per key is usually faster end to end than fanning out and being throttled.

If your use case does not fit

If you are considering several hundred sequential requests, a very high polling frequency, or a full historical extract, tell us what you are building before you build it. Those patterns usually have a better answer than more requests.

Last updated on