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 on | Use |
|---|---|
| A brand | Its domain, from GET /v1/brands |
| A competitor | Its domain |
| A cited page | The normalised URL |
| A prompt | The prompt text, plus its topic |
| A topic | Its 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.