Skip to main content
Argusdocs
Open Argus
Start here
Find sponsors with your agentConnect your agentConfirm, source and researchYour team workspaceEvidence and reportsResearch tool referenceTroubleshooting and FAQ
Argus
Today: the briefingFind: buying windowPrice: published ratesKeep: renewal radarProve: checks and recapsThe signal streamBrowseTrendsSelling seasonWatchlists, alerts, digestsCoverageCheck your sponsor listAsk ArgusEvent pagesCompany pagesRecaps and partner packs
The Operating System
Deal execution

Endpoints

Six read-only families under /api/v1/. Every response is the same envelope: your data plus a meta block naming the corpus freshness and your key's scope.

The response envelope

{
  "data": { ... },
  "meta": {
    "as_of": "2026-07-18T21:04:11Z",
    "scope": {"verticals": ["fintech"]}
  }
}

meta.as_of is the newest capture timestamp in the corpus. It dates the data, never the response: a row is as fresh as the crawl that observed it. scope echoes what your key is licensed for, either full_catalog or the vertical list.

as_of names three different quantities, and you should read the path

Some responses carry a second as_of inside data. It is not the same clock as meta.as_of, and the two can differ by an hour or more on a quiet corpus. There are exactly three quantities and each one always sits at the same path, so read the path rather than the word:

  • meta.as_of the newest crawl anywhere in the corpus. Corpus freshness. Present on every response of every family.
  • data.as_of on /movers and on /coverage?vertical= the newest observation behind THAT answer. It is a date from the record and is never today.
  • data.as_of on /buying-window the moment the answer was computed. The lens is a calculation over a rolling window, so its run stamp is what makes the window reproducible. It is not a claim about data freshness.

We are not renaming these. A rename would silently change the meaning of a field integrations already read, which is worse than an ambiguity you can now resolve by path. Documented 2026-08-02; no field changed.

These endpoints and the MCP tools no longer return exactly the same fields. Everything on this page describes /api/v1, which is unchanged. From 2026-08-10 an MCP tool result is this response projected through a serving list: every receipt, honesty flag and identity field still serves, and a handful of internal fields that no endpoint here documents (crawl_id, prev_crawl_id, section_shot, verification_detail, logo_url, the display-name overrides) do not. Tool results also have their string values cleaned of control and formatting characters and capped at 2000 characters. If you integrate over dk_ keys and this page, nothing about your integration changed. The detail is under MCP server.

GET /api/v1/signals

The dated change stream. Filters: vertical, types (comma-separated signal types), country, q, company_id, event_id, date_from, date_to. Cursor pagination: pass back next_cursor until it is null. Up to 200 rows per page.

curl "https://api.ducket.io/api/v1/signals?vertical=fintech&limit=50" \
  -H "Authorization: Bearer dk_example_9hK2mQ4vX8wL1pR7"

Each item carries its provenance block:

{
  "id": 4183,
  "type": "SPONSOR_ADDED",
  "headline": "Acme Pay appeared as Gold at Money20/20 Asia",
  "occurred_at": "2026-07-12T03:40:00Z",
  "company": {"id": 902, "name": "Acme Pay"},
  "event": {"id": 41, "name": "Money20/20 Asia", "vertical": "fintech"},
  "provenance": {
    "source_url": "https://money2020asia.example/sponsors",
    "observed_at": "2026-07-12T03:40:00Z",
    "verification": "anchored",
    "evidence_quote": "Gold Sponsors: Acme Pay"
  }
}

A SPONSOR_DROPPED item additionally carries provenance.prior_evidence_quote: the quote from the company's prior deal row on that event (what the page said while the company was still listed). Absence itself is never quotable, so the drop cites both the crawl the name is missing from and the prior sighting. Added 2026-07-22; additive, no version change.

A SPONSOR_DROPPED item's payload now also says how competently the absence can be read. Four keys, all inside payload:

  • edition_pending (true, false, or null). True when the edition the drop is claimed against has not started yet, so the roster is still being published and an absence from it is a hypothesis. Null when the event's dates on file cannot speak to that edition year, which is a different answer from false. It is evaluated when you read rather than when the item was minted, so an item minted before an edition opened reads false afterwards and the stored row is never rewritten. The headline of a newly emitted pending drop reads "not yet on the ... roster" with the start date, rather than the historical wording.
  • contradicted_by_active_listing (true, or the key is absent). Set when the corpus also serves an active listing for the same company at the same event and edition year, so two captures on file disagree. We label the disagreement rather than resolving it for you.
  • baseline_presence_only (true, or the key is absent). Set when every prior sighting behind the drop is press-class or sponsor-side. Such a page can establish a presence and can never establish an absence, so the drop rests on nothing observable.
  • baseline_not_serving (true, or the key is absent). Set when no sponsorship row for that company at that event serves any more, so the earlier edition named in the headline cannot be checked on its pages. Added 2026-08-07; additive, no version change.

These four keys are labels on rows that still serve. The product surfaces that recommend rather than record (the renewal radar, Hot Leads) exclude two of the classes and report their exclusion counts, but those are member surfaces and are not v1 endpoints. Added 2026-08-07; additive, no version change.

Changed 2026-08-07: a signal whose own deal row is held back no longer serves

/api/v1/signals now applies the same rule the roster endpoints already applied. A signal is minted from a deal row, and where that row is held back from the roster surfaces for one of the seven recorded reasons (see /api/v1/coverage below), the signal built on it is no longer returned. Before this, one corpus could report a company as having stopped sponsoring an event while every roster surface had already set aside the row the claim rested on. The same change applies to the MCP tool argus_signals, which routes to this endpoint.

This removes rows from result sets rather than changing any response shape, so it is not a version change and no field moved. A signal that names no deal row at all, such as a company-level signal, is unaffected. Nothing is deleted: the observation keeps its date, its page and its receipt, and it stays readable through the event and company endpoints as a held-back row.

GET /api/v1/events

The roster ledger at deal grain: one row per company, event, and edition year, each with its receipt. Filters: vertical, region, country, year, tier, group (sponsors, exhibitors, media), q. Offset pagination via limit and offset, up to 500 rows per page; the page block reports the unpaginated total.

Changed 2026-08-02: direction now refuses a value it does not know

direction accepts asc and desc, in any case. Until this change, every other value was silently treated as desc, so direction=ascending returned a confidently reversed page with no error. It now returns a 400 naming the value we rejected and the two we accept, matching sort beside it.

This is a behaviour change rather than an additive one, and it is the only one v1 has made. A request that previously got rows will now get a 400 if, and only if, its direction was outside the two-word vocabulary, in which case the rows it was getting were in the opposite order to the one it asked for. Omitting direction still means desc. The same rule applies on every ordered reader in v1, this endpoint included.

Changed 2026-08-11: q also matches the event organiser

q is matched against the company name, the event name, and now the organiser stated on the event page. So a query naming an organiser returns that organiser's whole portfolio on file, where before it returned nothing unless the organiser's name happened to appear in an event name. The same widening reaches the MCP tools argus_events and search, which route to this endpoint.

This is additive. No response shape changed, no field moved, and every row a query matched before it still matches. A query can now return more rows than it did, so if you page through q results, treat the total as a number that can grow. Matching is a plain case-insensitive contains on each of the three columns. It is deliberately not the organiser-family fold used to group an organiser's events together, which compares whole stated names.

GET /api/v1/events/{id}

One event: identity and location, per-edition receipted deals, sponsor counts over time, the recent signal timeline, and the workbench block (per-edition rosters plus a year-over-year diff that renders only when both editions were competently captured).

Added 2026-08-25: roster reach links (organisations with contact resolution)

For an organisation whose plan includes contact resolution, each deal row on this endpoint additionally carries company_linkedin (the company's LinkedIn page where a licensed vendor has supplied one, else null) and the payload carries a reach_links marker. Additive, no version change: any other key's response is byte-identical, with neither key present. The company's website needs no new field; every deal row has always carried company_domain.

The event object carries verticals: every industry the event is tagged into, primary first (vertical stays the primary and is unchanged). A vertical filter anywhere in v1 matches an event on any of its tags, and a vertical-scoped key can read any event carrying one of its licensed verticals. Added 2026-07-30; additive, no version change.

The event object also carries time_state: whether the event has already happened, worked out from the dates on file at the moment you read it. The values are past, happening_now, upcoming, dates_not_on_file and dates_unreliable. The last one is the important one: a great many event pages publish a bare year, a month with no day, a date in a local calendar, or a range whose end falls before its start, and every one of those reads as dates_unreliable rather than being guessed into a past or future. Treat the five values as a closed set and handle an unknown value as unclassified. Added 2026-07-31; additive, no version change.

The event object also carries organizer_relation: the organiser as a relation rather than a bare string. It is null when the event states no organiser, and otherwise an object with fold (the deterministic match key), organizer (the name as the page stated it) and other_events (how many other catalog events state the same organiser, a count on file and never a ranking). Two organisers match only on exact equality of the fold key, so a name that merely contains another never folds into it. The existing organizer string is unchanged and still present. Added 2026-07-31; additive, no version change. There is no v1 endpoint for the family listing itself. Since 2026-08-06 the fold matches per stated organiser, so an event whose page states two organisers folds into both families, and the relation may additionally carry events: up to six sibling events (id, name, start_date, time_state) in name order, while other_events stays the whole count. The event object may also carry site_relation (null or an object with identity, other_events and events): other tracked events published on the same registrable domain, for event brands that publish every city edition on one site without stating an organiser. It claims a shared site only, never a stated organiser, and events already covered by the organiser family are excluded. The company profile object gains organizer_of (null or an object with fold, organizer, count and events): the tracked events whose stated organiser matches the company's name, kept separate from its deal record. Like also_sponsors it is computed across the whole catalog, so a vertical-scoped key reads null. All three are additive, no version change. Two later additions, also additive: organizer_relation may carry company_id (the catalog company the organiser resolves to, null when none matches), family matching tolerates a trailing legal form on the STATED side only (an entry stating "Clutch Events" counts one stating "Clutch Events Limited", but not the reverse; see the note below and keep the direction in mind), and organizer_of may carry derived_domain (the shared registrable domain of the family's events, served only when the company record itself has no domain; a display fact, never an identity claim).

Since 2026-08-07 the organiser reads across a linked series. Where an event's own row states no organiser and a catalog entry folded into it does, that name is served for the event, and organizer_relation carries organizer_provenance: an object with event_id and name (the catalog row that stated it), source_url (that row's own source page, falling back to its url, and null when it carries neither), observed_at (the date that row was first read, null rather than guessed) and basis: self when the event's own row states it, member when a folded entry does. Read those two fields as identifying the stating ROW, never as the page the organiser name itself was parsed from: where a name was filled in later by fetching the event's home page, that page is not stored against the name, so the row's own source and first-read date are what the receipt carries. The event's own row always wins when both state one, and an event no row in its series states an organiser for still serves null. Additive, no version change.

Two notes on scope, corrected 2026-08-07. organizer_relation.events rows carry id, name, start_date and time_state only; they do not carry organizer_stated or organizer_provenance. Those two fields belong to the in-app organiser family listing, which is not part of this payload. And family membership tolerates a trailing legal form in one direction only: the relation is keyed on the fold of the string the row itself states, so an entry stating "Clutch Events" counts one stating "Clutch Events Limited" among its other_events, while an entry stating the longer form counts only entries stating it the same way.

Where two catalog entries for one event have been linked as a single series, both keep being crawled, so the same company can have been captured twice for one edition. deals carries each company once per edition year and commercial role: the most recent sighting, with its own observed_at, source_url and verification (a newer capture never inherits an older one's verification state). A company recorded in two different roles for one edition, a sponsor and an exhibitor, keeps both rows. kpis and sponsor_count_over_time count the same deduplicated set. An event with no linked series is unaffected. Changed 2026-07-31; the response shape is unchanged, so this corrects counts and rows rather than the contract, and there is no version change.

Each row in deals now says whether its evidence is the event's own roster or press coverage of it. source_kind is "roster" or "press", and a press row also carries a press object with source_class (one of organizer_own_press, tourism_gov_board, accredited_trade_press, pr_wire, unknown_press), plus the badge, label and note strings we render. An ordinary roster row carries press: null. A press row is a real, dated, receipted sighting and it counts towards presence, but it never counts as evidence that another company was absent, because an article names some sponsors and never the whole list. The class is assigned by code from the URL, never by a model. Added 2026-08-02; additive, no version change.

kpis is unchanged and now travels beside two blocks that say what it counted. kpis_split (keyed edition and all) breaks the sponsor count into first_party and press_reported, which always sum to active_sponsors, plus a ready-to-render sentence that is empty when there is no press row to split out. role_labels carries, per role group, the page's OWN word above each row: an ordered labels list of {label, n}, an unlabelled count for rows the page gave no label, and its own sentence. This matters because a sponsor count can be a third Partners and a third Supporting Partners; the count is right and it never used to say so. Neither block moves a number or re-buckets a row. Added 2026-08-02; additive, no version change.

Each sponsor_count_over_time year row now also carries by_group (sponsors, exhibitors, media; a company counts once per year, under its sponsor-most group), and the profile carries sponsor_count_note, a ready-to-render sentence saying the per-year totals count every commercial role; it is empty when every counted row is sponsors-group. The existing sponsors and dropped numbers keep their exact arithmetic. Added 2026-08-04; additive, no version change.

Each sponsor_count_over_time year row also carries held_back: how many rows Argus holds for that year that are held back from the roster surfaces. A year whose rows are ALL held back now appears with sponsors at zero and a non-zero held_back, where it used to be absent from the series entirely, so the year is distinguishable from one Argus never observed. The sentence naming those years is an in-app surface and is not a v1 field: this endpoint serves sponsor_count_note, which says what the bars count. A workbench edition's counts block carries the same held_back key, so an edition with a fully held-back roster keeps its place in editions instead of vanishing from the picker. Held-back rows enter no other count: such a year is still not a captured roster and is never compared against another edition. Added 2026-08-07; additive, no version change.

The workbench block now says, per row, whether the two editions behind an absence could be compared at all. Each not_seen row in editions[].diff carries pair_competent (boolean), false when the compared pair of full rosters shares no company whatsoever, which is the signature of two captures that describe different things. Beside it, editions[].diff.counts carries not_seen_not_comparable: how many of not_seen sit on such a pair, and zero when the pair is comparable. No row is withheld and no existing count changed shape: a false flag means Argus cannot tell whether the company returned, which is a weaker claim than the company having left. Added 2026-08-07; additive, no version change.

The profile also carries the event's containment edges: side_events lists the side events this event's own page advertised, and part_of names the umbrella it sits inside. Each edge carries the dated page that said so (evidence_url, observed_at) plus the far event's catalog facts. Rosters are never merged across a containment edge; a side event and its umbrella are separate events that share a schedule. On a vertical-scoped key, edges whose far event is outside your licence are omitted. Added 2026-07-30; additive, no version change.

GET /api/v1/companies/{id}

One company's observed record: identity, receipted deals across tracked events, and its signal timeline. On a vertical-scoped key the record is filtered to your licensed verticals server-side, counters are recomputed over the filtered rows, and a company with no licensed presence returns 403. Its deal rows carry the same source_kind and press fields described under events above, with the same meaning.

Added 2026-08-25: reach (organisations with contact resolution)

Organisations whose plan includes contact resolution get an additional reach block on this endpoint: links holds the company's website (derived from its stored domain) and its linkedin page where a licensed vendor has supplied one, each with a source and, for vendor links, an observed_at; contacts lists licensed-vendor contact records already on file for the company: person, title, role, email with the vendor's email_status verdict, linkedin_url, region, and the vendor plus date. Additive, no version change: a key whose organisation does not hold contact resolution receives a byte-identical response with no reach key, and no existing field moved. Lookups themselves happen in the app; this endpoint serves what is on file.

GET /api/v1/coverage

The transparency board: per vertical and region, how many events are tracked, how many carry rosters, and when each cell was last crawled. Add ?vertical= for the depth census of one vertical. Read this before reading any empty result as market truth.

Each cell now carries two deal counts, and they answer two different questions. deals is unchanged: rows ON FILE, everything we hold for that cell. deals_serving is the subset that appears on the roster surfaces, which is what /api/v1/events enumerates for the same slice. The difference is rows held back from the roster surfaces for a reason recorded against each one: the page it was cited from is not a sponsor roster, a second reading of the saved page did not find the company described there as a sponsor, the name sat under a section the page marks as a past edition or as something other than sponsors, the company is the event's own organizer, the list it came from names award winners or judges rather than sponsors, the page it was cited from is a profile page about a different company, or the page it was cited from belongs to a different event on the same website. Those seven reasons are the whole list (this page listed five until 2026-08-07; the two added here are the profile-page reason and the same-website reason). Those rows stay on file with a receipt and are never counted as sponsorships. A row that is merely unchecked is not one of them, and a press-reported row serves normally and is counted in deals_serving. A response-level census_basis sentence states both numbers and the gap, computed over the cells your key can read. Before this, the census and the ledger reported different totals for one slice and nothing on either surface admitted the other existed. Added 2026-08-02; additive, no version change.

GET /api/v1/buying-window

The buying-window lens over observed roster behavior. Params: vertical, country, window_months (6, 12, or 18). Rows state their coverage basis (re-crawled K of N in-window); where coverage is too thin the row says so instead of claiming a lapse. Added 2026-07-30: the response also carries a currently_active array with the same row shape, listing the qualified regular sponsors observed on a current sponsor list within the window, so the pool the counts describe is served in full. Additive; existing fields are unchanged.

GET /api/v1/movers

Entrant cohorts per event edition, sliced by geography, kind, and size, each member carrying its dated first-appearance receipt. Exactly one of vertical or event_id is required.

Error shapes

  • 400 a malformed filter, an unknown vertical, or a missing required parameter; the detail string says which.
  • A filter value outside its vocabulary is always a 400, never an empty page. This holds for vertical, region, country, tier, status, verification and types: the detail names the value we rejected and the values we accept. A country may be an ISO-3166-1 alpha-2 code or a country name, and we fold the name for you. Corrected 2026-08-02: region previously answered an unrecognised value with a 200 and zero rows, which reads as a finding rather than a typo.
  • A valid value the corpus holds nothing for is a 200 with zero rows. Absent and invalid are different answers, and only the second one is your mistake.
  • 401 every verification failure, see Authentication and keys.
  • 403 outside the key's licensed scope, with the detail outside this key's licensed scope.
  • 404 an id that does not exist in the catalog.
  • 429 rate or quota pressure, with headers, see Scopes, limits, usage.
Shapes are additive
Integrate against named fields and tolerate new ones. v1 adds fields over time; it never repurposes an existing one.