API changelog

Minor releases add to the contract (new route, optional parameter, response field, enum value); patch releases fix behaviour or docs. Within /v1 nothing is removed or changed in a breaking way: see the deprecation policy. The machine-readable contract is /openapi.json; the service state is on status.

1.39.0 · 2026-10-25

Device Watch portfolio tier for regulatory consultants and EU authorised representatives (additive; version taken after the gambling licence register 1.38.0). Market check and price anchors: docs/research/2026-10-25-srn-portfolio-market.md.

  • New POST /v1/watch/devices/bulk: follow many EUDAMED manufacturers at once from csv (every SRN in any column of a client-list export; other columns are ignored and never stored) or srns. Skips SRNs already followed; the answer's new bulk object counts added / already followed / over the limit and lists lines without an SRN. 403 device_watch_limit when nothing can be added over the limit; 400 too_many_rows above 5,000 lines.
  • DeviceWatches gains portfolio (tier or null) and frequency (daily free alert, weekly portfolio digest); limit is now per account: 10 free, 50 / 250 / 5,000 (fair use, "unlimited") with a portfolio tier.
  • GET /v1/plans gains devicePortfolio (free allowance, tiers with prices: Portfolio 50 €79/mo, 250 €199/mo, Unlimited €399/mo; yearly 10 x monthly). Sold by invoice for now (Stripe catalogue arcadium_device_portfolio_*), set on the account with pnpm api-key portfolio <account id> <tier>.
  • Weekly portfolio digest pnpm device-watch-send --portfolio (job device-portfolio-digest, Mondays 13:15 UTC): one email for all followed SRNs with the deadline countdown, the week's first devices, new UDI-DIs and certificate changes, manufacturers with no device yet, certificates expiring within 90 days and SRNs EUDAMED does not list. Portfolio accounts no longer get the daily alert. Needs migration ops/017_device_portfolio.sql (no new grants).

1.38.0 · 2026-10-25

Gambling licence register: is a website licensed for gambling, where and by whom (additive; version taken after pharma site register 1.37.0).

  • New tag Gambling and four routes over the official lists of six regulators (UK Gambling Commission, DE GGL whitelist, SE Spelinspektionen, DK Spillemyndigheden, ES DGOJ, NL Kansspelautoriteit). Needs migration gambling/001_init.sql, the grants in sql/readonly-role.sql and pnpm gambling load; without them the routes answer 503 unavailable.
    • GET /v1/gambling/domains/{domain}: per market the licence holder (company, address country, registry ids), its licences with status and validity, domain status (active / white label / inactive), licensed, match (exact host or same registrable domain), notListedOn with the day of each list read, and recent events. Anonymous: one domain per call (free tool tier).
    • POST /v1/gambling/domains/check: the same for up to 100 domains; needs a key; one metered row per domain.
    • GET /v1/gambling/operators: licence holders with licences, hosts and registry ids; filters q, regulator, country, includeRemoved. Anonymous: q required, five rows.
    • GET /v1/gambling/events: licence granted / suspended / revoked / ended / reinstated / removed, domain added / removed, operator listed / delisted; filters regulator, country, type, since. Needs a key.
  • GET /v1/plans: perCall entries may carry an optional unit (call or domain); the two licence-check routes are listed at EUR 0.05 (per check, per domain of a bulk call).

1.37.0 · 2026-10-25

Pharma site compliance register (additive; schema pharma, packages/pharma-sites; version taken after Merger Watch 1.36.0).

  • GET /v1/pharma/sites: EU pharma manufacturing and distribution sites from EudraGMDP (EMA public register) with counts of valid GMP / GDP certificates, MIA / WDA authorisations and active-substance registrations, GMP statements of non-compliance in force, matched FDA inspection classifications (NAI / VAI / OAI) and FDA drug warning letters, a status (non_compliant, fda_oai, warning_letter, certified, authorised, listed) and the company join (website domain, registry id, method, confidence). Filters country, domain, registry_id, status, since, q; cursor paging. Anonymous: domain or registry_id required, 10 rows.
  • GET /v1/pharma/sites/{id} (EudraGMDP site key or OMS location id): documents with links, non-compliance statements, FDA inspections, warning letters and change events.
  • Supplier Watch: new finding kinds gmp_non_compliance and fda_warning_letter (plus fda_oai_inspection) for supplier domains joined to a pharma site; sources.pharma tells whether the host has the schema.
  • Organisations only: no qualified / responsible persons, inspectors or authority contacts; sites whose name reads as a person are left out.

1.36.0 · 2026-10-25

Merger Watch: follow merger-control filings by company, sector, authority, country or keyword and get a daily email when a new filing or a status change matches (additive; version taken after device watch 1.35.0).

  • New tag Merger Watch: POST /v1/watch/mergers (body kind = domain | registry_id | nace | authority | country | keyword, value), GET /v1/watch/mergers, DELETE /v1/watch/mergers/{id}. The view shows tier and limit (total, companies, topics): free accounts keep 3 rules; the Pro plan carries Merger Radar (50 companies, 5 topics), Business and Scale Merger Radar Pro (1,000 companies, 100 topics). 403 merger_watch_limit above, 400 invalid_value for a value that does not fit its kind. Needs a self-serve key; not metered; nothing is billed.
  • New sign-up intent watch-mergers with exactly one of domain, registryId, nace, authority, country or q (new optional SignupIntent.registryId, .nace, .authority): the /mergers/ form's "Email me daily" signs up with it (double opt-in) and the verify page creates the rule.
  • Anonymous GET /v1/mergers also accepts nace or sector alone (still five cases): the /mergers/ form previews the filings of a rule without an email.
  • The daily pnpm merger-watch-send (job merger-watch, 07:30 UTC, after mergers-daily) emails an account only on a day with a matching event: notified, phase 2, cleared, cleared with conditions, prohibited, withdrawn, referred, closed (events dated more than 45 days back are a back-fill and skipped). A status move a source states without a dated event now writes an event of the load day. Needs migrations ops/016_merger_watch.sql and mergers/002_events_seen_at.sql, grants sql/ops-role-merger-watch.sql (pulled in by ops-role.sql).
  • GET /v1/account/export lists mergerWatches; account erasure deletes them.

1.35.0 · 2026-10-25

Device registration alert: follow an EUDAMED manufacturer and get an email when it registers devices (additive; version taken after mergers 1.34.0).

  • New tag Device Watch: POST /v1/watch/devices (body srn, or domain = every manufacturer SRN of that website), GET /v1/watch/devices, DELETE /v1/watch/devices/{id}. Free, up to 10 manufacturers per account (403 device_watch_limit above). Needs a self-serve key; not metered.
  • New sign-up intent watch-devices (with srn or domain; new optional SignupIntent.srn): the /eudamed/ checker's "Email me when it registers" button signs up with it (double opt-in) and the verify page creates the watch.
  • The daily pnpm device-watch-send (job device-watch, 12:45 UTC, after the EUDAMED refresh) emails an account only on a day with a change of a followed manufacturer: first device registered, new UDI-DIs, certificate issued / withdrawn / expired / expiring. One-click unsubscribe shared with the other digests. Needs migrations ops/015_device_watch.sql and devices/002_events_seen_at.sql, grants sql/ops-role-device-watch.sql.
  • GET /v1/account/export lists deviceWatches. Account erasure now also deletes job, recall and device watches (a free account with a job or recall watch could not be deleted before: the rows referenced the account); the api_ops grants for job_watches and recall_watches gain DELETE, and ops-role.sql now pulls in the supplier, recall and device watch grants.

1.34.0 · 2026-10-25

Merger filings radar: notified concentrations at three competition authorities (additive; version taken after master's 1.33.0).

  • New tag Mergers and two routes (needs migration mergers/001_init.sql and pnpm mergers load, runbook docs/runbooks/mergers-server.md; without the data they answer 503 unavailable): GET /v1/mergers (European Commission DG COMP open data, Bundeskartellamt current proceedings, UK CMA merger inquiries: reference, title, sectors, NACE codes (EC), normalised status pending / phase2 / cleared / cleared_with_conditions / prohibited / withdrawn / referred / closed with the authority's own label, phase, dates, case page and the company parties with role, stated country, resolved domain and registry id with match method and confidence; filters authority, country, sector, nace, status, since, party_domain, party_registry_id, party; paged by cursor) and GET /v1/mergers/{id} (one case plus its dated events).
  • Anonymous calls need authority, country, party_domain or party_registry_id and get at most 5 rows; one case is a point lookup.
  • Company-level only: parties that read as natural persons are never stored (counted in withheldParties, the title reads "[person]"); case handlers and advisers are not stored.

1.33.0 · 2026-10-25

Activation and quota nudges for Free keys (additive; version taken after consent radar 1.32.0).

  • Free keys from 80 % of the monthly quota get two response headers: x-quota-warning (80, or 100 from the last call of the quota on) and x-upgrade-url (the plans page). Both are exposed to browsers (CORS). Paid plans are not affected.
  • The quota_exceeded 429 of a Free key carries an optional upgrade field in the error body (url, message, nextPlan, monthlyQuota, priceEurPerMonth); the Error schema lists it.
  • Quota hits are recorded once per account, threshold and month (migration ops/014_activation.sql, table ops.quota_hits); the hourly pnpm api-onboarding-send sends one email per threshold and month, plus the onboarding emails (welcome, day-2 first-call nudge while no call was made, day-7 usage summary). All carry the one-click unsubscribe of the digests and respect it.

1.32.0 · 2026-10-25

Consent radar: which consent-management platform and trackers company websites load (additive; version taken after emissions 1.31.0).

  • New tag Consent and three routes (needs migration consent/001_init.sql and pnpm consent-scan, runbook docs/runbooks/consent-server.md; without the data they answer 503 unavailable): GET /v1/consent/market (CMP vendor shares and tracker prevalence per country or segment, adoption, counts per static-HTML signal and the monthly history), GET /v1/consent/companies/{domain} (CMPs, trackers with category, tracker scripts with no consent hold-back marker in the delivered HTML, Consent Mode / TCF flags, scan history and changes) and GET /v1/consent/switches (CMP switched / added / removed between two monthly scans; filters domain, from_cmp, to_cmp, country, kind, since; paged by cursor).
  • Anonymous calls: market is a preview (one country or segment per call), a company stack is a point lookup, the switch list needs domain (5 rows) or a key.
  • Technical detection of public HTML only (one plain GET of the home page per month, plus the public GTM container script when the page loads a tag manager and no CMP). No verdicts: the "scripts without consent gate" field is a plain-fetch observation. No personal data.

1.31.0 · 2026-10-25

Company-level emissions for CBAM and financed-emissions reporting (additive; version taken after master's 1.30.0).

  • New GET /v1/emissions/companies (searchEmissionsCompanies): operators (legal entities only) of the EU Emissions Trading System, largest emitter first, cursor pagination. Per operator: verified emissions and free allocation per year since 2013, the allocation gap of the latest year (allowances to buy; CBAM phases free allocation out from 2026), installation count, sectors and CBAM goods sectors (cement, iron_steel, aluminium, fertilisers, hydrogen), the five biggest installations, and the join to LEI, website domain (domainMethod: lei or registry_id) and ultimate parent LEI and domain (GLEIF Level 2) where the EU Company ID table proves the link. Filters: domain (the operator's own or its ultimate parent's: group view with matched: parent), lei (operator or ultimate parent), country, sector. Anonymous callers: one of domain, lei or country, 5 rows, no cursor.
  • New GET /v1/emissions/installations/{id} (getEmissionsInstallation): one installation, aircraft operator or shipping company (ids like eutl:DE:975) with yearly emissions and allocation, the Union Registry compliance code (A, B, C, EXCLUDED SINCE year, with a complianceLabel), its operator, change events (new installation, emissions jump or drop of 25% and more from 10,000 t, compliance change, account closed) and, where the Industrial Emissions Portal facility states the ETS id, its parent company and latest pollutant releases.
  • MCP tool company_emissions on the same data. Sources and licences: European Commission Union Registry (CC BY 4.0, "Source: European Commission, Union Registry of the EU ETS"), EEA Industrial Emissions Portal (CC BY 4.0, European Environment Agency), GLEIF (CC0). Account holders that are not companies are left out; no coordinates or addresses.

1.30.0 · 2026-10-25

Partner referral code (additive; version taken after Planning 1.29.0).

  • POST /v1/account/signup accepts an optional ref: the partner code from a ?ref= link (3-32 lowercase letters, digits or hyphens; anything else is ignored, never an error). It is kept with the emailed link, stored once on the new account (first code wins) and copied into the Stripe Checkout session and subscription metadata as ref.
  • GET /v1/me account.referralCode: the partner code the account signed up with, or null.
  • Needs ops migration 013_referral.sql (two nullable columns, one index). The partner statement (pnpm partner-statement --month YYYY-MM, docs/partners.md) joins Stripe invoices to the code.

1.29.0 · 2026-10-25

Planning: UK and Ireland construction project leads (additive; version taken after Recall Radar 1.28.0).

  • New tag Planning and two routes (needs migration planning/001_init.sql and pnpm planning, runbook docs/runbooks/planning-server.md; without the data they answer 503 unavailable): GET /v1/planning/applications (non-householder commercial and large residential planning applications: authority, reference, description, type, kind, units / floor area, status submitted / granted / refused / withdrawn / appealed / decided, dates, town and postcode district, the authority's record URL and the company applicant with Companies House number and domain where published; filters country, authority, type, kind, status, min_units, min_floor_area, changed_since, with_applicant; paged by cursor) and GET /v1/planning/companies/{domain} (planning activity of one developer plus its construction-works contract awards from the tender index). Anonymous calls need country or authority and get at most 5 rows. There is no min_value: none of the open feeds publishes a project cost.
  • Legal entities only: an applicant that reads as a natural person is never returned (and never stored); agents, architects, case officers, e-mail addresses, phone numbers and street addresses are not stored. Ireland's dataset publishes no applicant at all. Sources: National Planning Applications (Department of Housing, CC BY 4.0), London Borough of Camden, City of Bradford MDC and Northumberland County Council (Open Government Licence v3).

1.28.0 · 2026-10-25

Recall Radar (additive): product safety alerts and the marketplace listings that match them.

  • GET /v1/recalls: EU Safety Gate, US CPSC and UK OPSS alerts, normalised (product, brand, model, GTIN barcodes with a verified check digit, category, risk type, measures, the marketplace listings the authority names, links to the official page and images). Filters q, source, country, category, brand, riskType, barcode, since, until, withMatches; cursor pagination. Anonymous: with brand, barcode or q, at most 10 rows.
  • GET /v1/recalls/matches: live Amazon, AliExpress, Etsy and TikTok Shop listings that match an alert, by the listing the alert names (confidence 0.99), by barcode (0.97) or by brand plus model code or name (0.80-0.93). Each match carries notice ("Listing matches a recall alert. Verify before acting."), listing URL, price, the marketplace seller id (never a name) and first / last seen. Filters marketplace, brand, category, alertId, minConfidence, since. Anonymous: with brand or alertId, at most 5 rows.
  • Recall Watch: POST|GET /v1/watch/recalls, DELETE /v1/watch/recalls/{id} (self-serve key, up to 5 brands, weekly email pnpm recall-watch-send, same one-click unsubscribe as the other digests). Needs migration 012_recall_watch.sql and apps/hiring-api/sql/ops-role-recall-watch.sql.
  • Data: schema recalls (packages/recalls), loaded weekly by pnpm recalls load; runbook docs/runbooks/recall-radar-server.md.

1.27.0 · 2026-10-25

Medical devices: EU EUDAMED x US FDA (additive; version taken after energy 1.26.0).

  • New tag Medical devices and three routes (needs migration devices/001_init.sql and pnpm devices, runbook docs/runbooks/devices-server.md; without the data they answer 503 unavailable): GET /v1/devices/manufacturers (manufacturers registered in EUDAMED: SRN, seat, website domain, registry / VAT ids of the domain, devices by risk class, valid certificates and notified bodies, EU authorised representative flag for non-EU manufacturers, matched FDA establishment registrations; filters domain, srn, q, country, risk_class, us_cleared, no_eu_ar, cert_expiring, no_devices), GET /v1/devices?manufacturer_domain= or manufacturer_srn= (UDI-DI rows of one manufacturer with its certificates and FDA matches; paged by cursor) and GET /v1/devices/events (new registrations, certificates issued / expiring / withdrawn; key required). Anonymous calls need domain/srn (or manufacturer_domain/manufacturer_srn) and get at most 10 rows. These routes use snake_case filter names (manufacturer_domain, risk_class) as specified for the product.
  • Legal entities only: economic operators whose name reads as a natural person are never returned; contact persons, e-mail addresses, phone numbers and street addresses are not stored (the website domain is the registrable domain of the published mailbox host). Sources: EUDAMED public data (European Commission, reuse under Decision 2011/833/EU) and openFDA registration & listing (public domain).

1.26.0 · 2026-10-25

Grid-connection and renewable project pipeline (additive; version taken after master's 1.25.0).

  • New GET /v1/energy/projects (searchEnergyProjects): wind, solar, storage, biomass and hydro projects of 1 MW and up, largest first, cursor pagination. Filters: country (DE, GB), technology, minMw, status (planned, permitted, under_construction, in_operation, shut_down, decommissioned), changedSince (status, capacity or dates changed in a load cycle), region (DE Bundesland), operatorDomain. Germany: Marktstammdatenregister units incl. planned ones, Bundesland / district / municipality, grid operator, operating company with website domain and registry id where known. Great Britain: NESO Transmission Entry Capacity register and Embedded register (Gate 1/2, consent stage, connection date). Companies only: natural-person operators are left out, no coordinates, no street addresses. Anonymous callers: one country or technology, 10 rows, no cursor.
  • New GET /v1/energy/projects/{id} (getEnergyProject): one project with its change history (new project, status change, capacity change, moved date, removed), ids like de_mastr:SEE940146675093, gb_tec:PRO-003804.
  • MCP tool energy_projects on the same data. Sources and licences: Marktstammdatenregister (Bundesnetzagentur, DL-DE-BY-2.0) and NESO Open Data Licence ("Supported by National Energy SO Open Data").

1.25.0 · 2026-10-25

Clinical-trial pipeline by sponsor (additive; after Supplier Watch 1.24.0).

  • New GET /v1/trials (listTrials): trials from ClinicalTrials.gov (public domain) and the EU CTIS public portal, one record per trial (records that cite each other, by NCT / EU CT / EudraCT / ISRCTN number, are merged), newest change first. Filters: sponsor (company website domain), sponsorClass, phase, status, condition (condition or MeSH words), area (condition area), country, changedSince; cursor paging. Each trial: ids, title, phase, status, conditions, MeSH terms, intervention types, countries, start and primary completion date, enrolment target, number of sites, sponsor organisation, type and company domain. Anonymous: only with sponsor, condition or area, a 5-row preview, one value of phase / status / country, no cursor.
  • New GET /v1/trials/sponsors/{domain} (getTrialSponsor): the pipeline of one sponsor company: trials by phase, status, area and country, trials first posted in the last 90 days, its newest trial changes (new trial, phase move, status change, moved dates), open clinical and regulatory roles on its own career sites, public contract awards and registry ids. Anonymous point lookup.
  • Organisation-level only: no investigators, contacts, e-mails, phones or site names; a sponsor that could be a private person is withheld. New schemas Trial, TrialsPage, TrialChange, TrialSponsorProfile; new tag Clinical trials. MCP tool clinical_trials reads the same routes. ISRCTN is not served (its terms forbid building a database).

1.24.0 · 2026-10-25

Supplier Watch (additive): a supplier list with a risk digest by email, and a point lookup.

  • POST /v1/watch/suppliers (self-serve key): upload up to 2,000 suppliers per call as CSV text (csv) or JSON rows (suppliers): a website domain, a vat number with its country prefix, a registry id + country, or a registered name + country. The answer carries a match report (match): rows matched / ambiguous / not found / withheld (sole traders and natural persons are withheld and never stored), invalid rows, duplicates, suppliers added and suppliers left out by the plan limit. The list size is the plan's tracked-company allowance (GET /v1/plans trackedCompaniesIncluded); over it the call answers 403 supplier_limit.
  • GET /v1/watch/suppliers (paged with limit / offset) and DELETE /v1/watch/suppliers/{id}.
  • The account email gets a "Supplier risk digest" (pnpm supplier-watch-send): weekly on the chosen weekday, daily on Pro, Business and Scale. Per supplier, the changes since the last digest ranked dissolved / insolvency / strike-off > VAT invalid > unreliable VAT payer > accounts overdue > contract won > hiring freeze, each with a link to the official source. Same one-click unsubscribe as the other digests.
  • GET /v1/suppliers/risk?domains= (anonymous point lookup: one domain without a key, up to 10 with one): the company behind each domain, the stored register status and the ranked findings of the last days (default 90). Stored data, no live call.
  • Migration 011_supplier_watch.sql (ops.supplier_lists, ops.supplier_watches); grants in sql/ops-role-supplier-watch.sql and the taxflags / vies_checks block of sql/readonly-role.sql.

1.23.0 · 2026-10-25

Final prices (additive, one plan-table change).

  • New self-serve plan scale (EUR 999 a month): 4,000,000 calls a month, 1,200 requests a minute, 200,000 tracked companies (4x Business calls and tracked companies at 2x the price). POST /v1/account/checkout and the sign-up plan intent accept "scale"; GET /v1/plans lists it with its monthly and yearly price. Needs migration 010_scale_plan.sql (plan checks) and pnpm stripe-setup (prices arcadium_api_scale_month|year) before it can be bought.
  • Yearly billing is now "2 months free" for every paid plan: 10 times the monthly price (Starter 490, Pro 1,990, Business 4,990, Scale 9,990), annualDiscount is 1/6 (was 0.15, prices were 15% off). New fields in GET /v1/plans: annualFreeMonths (2) and founding (the founding price: code FOUNDING29, Starter at EUR 29 a month for 90 days, first 100 accounts; a Stripe promotion code with max_redemptions 100, entered at checkout). Existing yearly subscriptions keep the price they bought.
  • Bid Desk (EUR 390 / 890 a month, 3,900 / 8,900 a year) and the data licences are billed outside this API, by Stripe payment links and Stripe Invoicing created with pnpm stripe-setup. They have no API route.

1.22.1 · 2026-10-25

Defence sector precision (behaviour fix, same contract).

  • sector=defence now means defence and security relevant procurement only. A defence ministry's or armed force's purchases of civilian commodities (medicines, food, fuel, furniture and cleaning, printed matter, office supplies, catering, health and cleaning services, ordinary clothing, transport) no longer match unless the notice states Directive 2009/81/EC, a defence or security CPV code or a military / security word in the title. Those notices carry a separate internal flag (defence_buyer_other) that is not exposed by the API. Notice counts under sector=defence drop by about 29% after the server re-classification (docs/runbooks/defence-reclassify.md). Rule and audit: docs/research/2026-10-24-defence-classifier-audit.md.

1.22.0 · 2026-10-25

GDPR self-service (additive).

  • GET /v1/account/export (key auth, not metered, 10 an hour): everything held for the account as JSON: email, company domain, plan, dates, billing state, key metadata (never the secrets), watchlists, Competitor Watch and Tender Watch settings, usage counters per month and route.
  • DELETE /v1/account with {"confirm":"delete"} (key auth, 3 an hour): cancels a paid Stripe subscription at once when billing is on (a Stripe failure answers 502 and deletes nothing), revokes and unlinks every key, deletes watches and watchlists and erases the email. An account that ever had a Stripe customer keeps, without the email, only the customer id, company domain, plan and dates for 10 years (retained in the answer); any other account row is deleted.
  • Retention: sign-up links are deleted after 48 hours, unverified accounts after 48 hours (daily pnpm api-anon-watch; needs sql/ops-role-retention.sql on the server). See docs/gdpr.md.

1.21.0 · 2026-10-25

Remote jobs, direct from employers (additive; version taken after master's 1.20.0).

  • New GET /v1/jobs/remote (searchRemoteJobs): the free search behind https://arcadiumdata.io/jobs/remote/ and the MCP tool remote_jobs. Same rows and the same stored-tsvector / in-flight-cap path as GET /v1/jobs, with remote forced on, region (europe, uk, us, worldwide = remote jobs that list no country) and days (first seen in the last N days). Anonymous behaviour (owner decision 2026-10-25): free and ungated on this route only. Anonymous callers get up to 50 jobs per call and may page with cursor; they stay under the per-IP and subnet limits, the in-flight cap and the route's daily anonymous budget (20,000 database reads a day, then cache-only). GET /v1/jobs keeps its rule (key required). New x-anonymous wording: "anonymous with paging".
  • GET /v1/jobs gets an optional seenSince (YYYY-MM-DD): first seen by us on or after that day. Unlike postedSince it also covers jobs without a posting date.
  • New Job Watch (weekly email alerts for a saved remote-job search): POST/GET /v1/watch/jobs, DELETE /v1/watch/jobs/{id} (up to 5 per account, daily is refused with 403 plan_required until a paid plan exists). New sign-up intent watch-jobs (q, region, frequency).
  • Sign-up behaviour change: companyDomain is optional on POST /v1/account/signup when the intent is watch-jobs (a job seeker has no company website). The account then stores the reserved placeholder jobseeker.invalid. Freemail addresses were already accepted as the sign-in email; the disposable-inbox refusal and the sign-up rate limits are unchanged. Every other sign-up still needs a company domain that is not freemail.

1.20.0 · 2026-10-25

UK Companies House register (additive).

  • GET /v1/companies/resolve matches carry sectorCodes (array or null): the register's sector codes, GB up to 4 SIC 2007 codes with the primary first, CZ the NACE code. statusSource can now be gb_ch.
  • Great Britain is answered from the monthly Companies House snapshot of live companies (gb_ch, Open Government Licence v3.0): resolve by Companies House number (type=uk_crn or country=GB), by name, and by a domain whose legal notice states the number returns legal name, category, status and SIC codes.
  • GET /v1/companies/verify?id=<number>&country=GB answers from that snapshot: register.source is gb_ch and the new optional register.basis is snapshot (other registers answer live). A number that is not in the snapshot (dissolved before we held it, or never registered) answers not_found; officers and persons with significant control are never read or returned. Needs registers migration 005_gb_ch.sql and pnpm registers-gb-ch (runbook docs/runbooks/uk-companies-house-server.md).

1.19.0 · 2026-10-25

UK gender pay gap and licensed visa sponsors on the company profile (additive; 1.18.0 is the billing flag on master).

  • GET /v1/companies/{domain} gains two optional fields, both null when none or on a host without the data: ukPayGap (latest UK gender pay gap report of the company, joined through its Companies House number: median and mean hourly gap, bonus gaps, share of women in the top quartile, size band, late flag, the employer's own report page, history by year) and visaSponsor (entry on the Home Office register of licensed sponsors: routes, ratings, town, register date). Company level only; the person named in the filing is never stored. Both are Open Government Licence v3.0 data. Needs registers migration 004_uk_open.sql and pnpm uk-open (runbook docs/runbooks/uk-paygap-sponsors-server.md).

1.18.0 · 2026-10-25

Billing visibility (additive).

  • GET /v1/plans and GET /v1/me return billing: boolean: true when self-serve checkout works on this host, false when paid plans are not open yet (checkout answers 503). The site hides the upgrade buttons when it is false.

1.17.0 · 2026-10-25

UK Contracts Finder (additive).

  • source accepts a new value cf (UK Contracts Finder: England's below-threshold opportunities and awards, plus legacy PCR 2015 notices) on GET /v1/tenders and in Tender.source. No new route or field: cf rows come through /v1/tenders, /v1/tenders/expiring (award rows with a contract end), /v1/tenders/leaders and the MCP tender tools like any other source; buyer country is GB, supplier ids are uk_crn: (Companies House) where the notice states one. A Contracts Finder notice that copies a Find a Tender notice (same buyer, title and kind within 3 days, no contradicting value or deadline) is not listed twice: the CF row is a duplicate of the FTS row.
  • Supplier names without a Companies House number must carry a company or public-body form to be listed (sole traders are counted in winnersWithheld).

1.16.0 · 2026-10-25

UK Procurement Act notice views (additive).

  • GET /v1/tenders/expiring: new optional kind = pipeline (UK1 pipeline and UK3 planned procurement notices), terminated (UK11 contract termination) or changed (UK10 contract change), UK Find a Tender only. The from / to window applies to the contract end the notice states. Without kind the answer is unchanged (award notices).
  • ExpiringContract rows gain optional noticeCode (Procurement Act 2023 notice code UK1..UK17; null for other sources). Needs ops migration tenders/009_notice_code.sql and a re-read of the UK days (runbook docs/runbooks/uk-fts-backfill-server.md).

1.15.1 · 2026-10-25

  • POST /v1/account/signup answers 503 signup_closed on a production host without SMTP (magic links would only reach the log). Same contract; every other route and the anonymous tier are unchanged.
  • POST /stripe/webhook refuses an event whose livemode does not match the host (400 livemode_mismatch), and a production host with a Stripe test key runs with billing off unless STRIPE_ALLOW_TEST=on.

1.15.0 · 2026-10-24

Defence & Security Procurement Radar (additive).

  • New optional sector on GET /v1/tenders, GET /v1/tenders/expiring and GET /v1/tenders/leaders: defence = defence and security procurement (defence ministries, armed forces and their procurement and logistics agencies, TED notices under Directive 2009/81/EC, military CPV codes, police / border / customs / prison buyers of security equipment; fire brigades, civil protection and military hospitals left out). Precision of the classifier on a hand-checked sample of 200 notices: see docs/research/2026-10-24-defence-classifier-audit.md.
  • GET /v1/tenders/expiring: new optional frameworksOnly=true (framework agreements only).
  • GET /v1/tenders/leaders: sector counts as the narrowing filter (no country / cpv / q needed) and is always answered live; filter.sector echoes it; winner rows gain registryId (type:value) when the notices name the winner alone with one company id (companies-only rule of 1.14.0; null otherwise, absent from rollup answers).
  • POST /v1/watch/tenders: optional sector (defence); a saved search may be the sector alone. TenderWatch rows carry sector. Sign-up intent watch-tenders accepts sector. Needs ops migration 007_tender_watch_sector.sql.

Also in this release (no contract change):

  • Performance (security review R1, slow queries an anonymous caller could trigger): GET /v1/jobs?q= runs on a stored search vector in two phases once the server's backfill is done (same matches, same order); minValueEur on GET /v1/tenders and GET /v1/tenders/expiring uses an index; GET /v1/tenders/leaders with q reads the matching notices once instead of three times; POST /v1/coverage counts in the database. The only visible difference: pay bands with the same jobs and currency are now ordered by period (was arbitrary).

1.14.0 · 2026-10-24

EU Company ID: companies only for registry ids (additive field, stricter data).

  • GET /v1/companies/resolve: new field withheld (count of ids left out). An id a sole trader or natural person can also hold (FR SIREN / TVA, CZ IČO, IT partita IVA, PL NIP / REGON, DE HRA and VAT, NL KvK, BE KBO, AT FN, NO orgnr, FI Y-tunnus, other VAT numbers) is returned only when a stored register row with a company legal form (FR SIRENE / BODACC, NO Brønnøysund, FI PRH, PL KRS) or a GLEIF record holds it, or, for countries without a stored register, a company-only id of the same country (DE HRB, UK company number, PL KRS, ES entity NIF, SE organisationsnummer) sits on the same domain. A person's own number by pattern (CZ VAT of 9-10 digits, BG VAT of 10, PT 1xx-3xx, LV personal codes, ES DNI/NIE) is never returned. An id query that only matched a withheld id answers found: false with withheld > 0.
  • GET /v1/companies/{domain}: registryIds follows the same rule (shape unchanged).

1.13.0 · 2026-10-24

EU Company ID (additive).

  • New GET /v1/companies/resolve (CompanyResolve): exactly one of domain, name + country, or id (+ optional type: vat, lei or a national id type, or country). Stored data: the company's domain, registry / VAT / LEI ids with sources, confidence and observedAt, VAT, LEI with its GLEIF status, legal name, legal form, country and the status the national register last showed (NO, FI, FR), asOf. Nothing found = 200 with found: false. Anonymous: point lookup, at most 3 matches.
  • New GET /v1/companies/verify (CompanyVerify): vat, or id + country. Live check in the open national register (NO Brønnøysund, FI PRH/YTJ, FR recherche-entreprises + stored BODACC, CZ ARES) and in the European Commission's VIES for the VAT number, cached 24 hours. status adds not_found, not_company (sole trader / natural person: nothing else is returned) and unknown (sources did not answer); statusBasis says where the status comes from. Anonymous: point lookup with its own lower daily budget (300 database-miss calls a day).
  • Both routes sit before /v1/companies/{domain}, which therefore no longer treats resolve / verify as a domain (neither was a valid company domain before).
  • GET /v1/plans: new field perCall (routes priced per call; /v1/companies/verify placeholder €0.02, counted as one quota call until per-call billing is switched on).

1.12.0 · 2026-10-24

  • POST /v1/account/signup: new optional body field intent (SignupIntent): what to set up after the email is confirmed, a free-tool action (watch-company + domain, watch-tenders + country / cpv / q, pay-csv + role) and/or a paid plan to upgrade to. It is kept with the emailed link (24 hours) and an invalid one is refused with 400 before any email goes out.
  • POST /v1/account/verify: new response field intent (the intent sent with the sign-up, or null), so opening the link on another device still finishes the setup.

1.11.0 · 2026-10-23

  • GET /v1/companies/{domain}: new response field registryIds (national registry, VAT and LEI ids of the company, with type, canonical value, confidence and sources: the company's own legal-notice page, Wikidata, GLEIF, national registers). A domain known only by its registry ids now answers 200 instead of 404. Point lookup only (anonymous point rule unchanged): no route lists or searches ids, so nothing new is listable without a key; at most 20 ids per domain.
  • Company Pulse: tender_awarded evidence match may now be registry_id (the winner's national / VAT id on the award notice equals an id the company states), as exact as website.

1.10.0 · 2026-10-23

Policy change for anonymous callers (no key); the contract for keyed and service callers is unchanged (same routes, parameters, fields, page sizes and limits). Which calls work without a key is policy, not contract (docs/api-versioning.md "Anonymous tier"), so it changes within /v1.

  • Anonymous = point lookups only. Every auth: optional operation states its rule in a new x-anonymous extension. GET /v1/jobs, GET /v1/pulse/events and GET /v1/events need a key (401 key_required); GET /v1/ats/switches needs domain, GET /v1/tenders needs winnerDomain, GET /v1/pay/bands needs role or isco (one country, one group), GET /v1/reviews/panel needs one ticker or q; GET /v1/tenders/leaders, GET /v1/ats/market and GET /v1/pay/transparency take one value per filter. cursor always needs a key.
  • Anonymous answers are a preview: at most 5 rows per list on list and aggregate routes (14 nights of a rate curve, 28 panel rows), nextCursor always null, descriptions cut to 280 characters, x-arcadium-anon: true on every anonymous answer and a new anonymous field (preview, rowCap, truncated, signup, note).
  • POST /v1/coverage: at most 10 domains per call without a key (50 with one).
  • New limits without a key: per subnet (IPv4 /24, IPv6 /48) on top of per IP, a daily budget of anonymous database reads per route, and strict mode (503 anon_strict, Retry-After: only recently cached answers) when a budget runs low, after repeated global-cap trips, or when the operator sets ANON_MODE=strict. The global anonymous cap is now 300 calls/minute (was 1,200). Optional deny list of hosting/proxy networks for anonymous calls (401 key_required).
  • GET /v1/plans: new field anonymousPolicy. OpenAPI: new 503 response on operations open to anonymous callers.
  • Share-type fields (share, payShare, remoteShare, ...) may differ from the stored value below the 6th decimal; rounded to 6 decimals they are exact.
  • New tender routes of 1.9.0 without a key: GET /v1/tenders/expiring and GET /v1/tenders/markets are previews (one country and one cpv, top 5 rows per list, no cursor); GET /v1/tenders/suppliers/{id} is a point lookup with lists capped at 5.

1.9.0 · 2026-10-23

  • Tenders: GET /v1/tenders/expiring (new, Re-tender Radar): awarded contracts whose contract ends between from and to (default today + 18 months), soonest first, with current holders (+ website domain when stated), value band (framework ceilings flagged), end-date basis, renewals and the expected re-tender window; cursor pagination.
  • Tenders: GET /v1/tenders/suppliers/{id} (new, Contracts Intelligence): supplier profile by website domain or supplier key: awards, buyers, buyer concentration (HHI), CPV divisions, countries, monthly timeline, contracts ending in the next 18 months, latest awards.
  • Tenders: GET /v1/tenders/markets (new): one market (country and/or cpv): top winners and buyers, HHI of award and value shares, top-1/3/10 share, median award, award vs estimated value, cross-border share, repeat buyer-supplier pairs, awards per month.

1.8.0 · 2026-10-23

  • Free tools tier (new): the read routes behind the free site tools (/tools/*, /lookup/<domain>/) are marked x-tool: true in the OpenAPI document. Anonymous calls there use their own per-IP bucket (30/min, 300/day instead of 10/min, 100/day), may be answered from a shared 10-minute answer cache (x-cache: hit|miss, age) and carry s-maxage + stale-while-revalidate. Every anonymous call that reaches the database also counts against one global per-minute cap (429 "busy"; keys never count). Limits in GET /v1/plans anonymousTools (new field). Routes: companies profile, history and pulse, ATS switches, pay bands, tenders, tender leaders, shops.
  • GET /v1/tenders/leaders (new): top winning organisations and top buyers of award notices for a country, cpv and/or q in the last months (default 12), plus the 5 newest award notices.
  • GET /v1/tenders: new optional winnerDomain (notices won by a company domain).
  • GET /v1/ats/switches: new optional domain (switches of one company).
  • GET /v1/pay/bands: new optional role (a job title, mapped to its ISCO-08 group); response field role.
  • Tender Watch (new): POST /v1/watch/tenders (countries, CPV prefixes, keywords, weekday), GET /v1/watch/tenders, DELETE /v1/watch/tenders/{id}: a weekly email with the new awards and open calls of a saved search (free during the beta, 5 per account; pnpm tender-watch-send, migration 004_tender_watch.sql).
  • GET /badge/hiring/{domain}.svg (new, outside /v1): an embeddable SVG badge with the open-jobs count, cached 1 hour, global cap only (a busy host serves a neutral badge).

1.7.0 · 2026-10-23

  • Company Pulse: new event types from official company registers and EU grants, keyed to the company domain the register states (or a VAT / name + country match, see evidence domainMatch and domainConfidence): register_registration, register_bankruptcy, register_restructuring, register_liquidation, register_dissolution, name_change, hq_moved, capital_increase, capital_decrease, headcount_band_change, sector_change, accounts_filed, business_sale (France BODACC: the company bought a business) and eu_grant_won (CORDIS Horizon Europe, with ecContributionEur). Registers: Norway Brønnøysund, Finland PRH, France SIRENE + BODACC. Evidence carries register, registryId, company and a url on the register; never a person.
  • Pulse event source adds registers and eu_grants; sources (PulseSources) adds the same two booleans.
  • Score: two new components, corporate (max 10: insolvency, liquidation and dissolution count against, headcount band up / capital increase for) and public_funding (max 6, EU grants).

1.6.0 · 2026-10-23

  • Competitor Watch (new): a weekly email digest per competitor domain (hiring by function and country against a 4-week baseline, new countries, tools named in job ads, posted pay, reviews, public contracts won, ATS switches, each with dated evidence links). POST /v1/watch/competitors (domains + weekday), GET /v1/watch/competitors (watches, limit, freeMonth, unsubscribed), DELETE /v1/watch/competitors/{domain}, POST /v1/watch/checkout (Stripe Checkout for the add-on: competitor_watch_single per competitor or competitor_watch_10) and POST /v1/watch/unsubscribe?token= (RFC 8058 one-click, no key). Needs a self-serve key; not metered.
  • GET /v1/plans: new field addons (Competitor Watch prices, free first month).

1.5.0 · 2026-10-23

  • Tenders: new response field winnerDomains on every tender (GET /v1/tenders and the tender objects elsewhere): the company domains of the winners, from the organisation websites the notice states (TED winner internet address, attributed to the winner names; possible persons, buyers, portals and public-sector sites dropped). Empty when the notice states none. Use it to join a tender to /v1/companies/{domain}.
  • Company Pulse: tender_awarded evidence match is now website (stated winner site) or company_name (unique name key + country, as before); its winner may be null when the stated site spells no winner name. Review events may carry method: stated (the developer site the App Store / Google Play listing states).

1.4.0 · 2026-10-23

  • ATS market (new): GET /v1/ats/market (monthly ATS share since February 2023 among the employers observed in the public web archive, wins / losses / dated switches per vendor, one segment per call by country or sizeBand, series=core4|all5, plus today's live share across 10 ATS) and GET /v1/ats/switches (companies that switched ATS, keyed by domain, dated, with evidence and confidence; filters fromAts / toAts (aliases from_ats / to_ats), since, country, includeLeverGap; cursor paging).

1.3.0 · 2026-10-23

  • Account (new): self-serve sign-up and billing. POST /v1/account/signup (work email + company domain + accepted terms; emails a one-time link, 202), POST /v1/account/verify (redeems the link, returns a Free key once), POST /v1/account/checkout (Stripe Checkout for Starter, Pro or Business, interval month or year), GET /v1/account/portal (Stripe Customer Portal). Checkout and portal need a key but are not metered.
  • GET /v1/me: new fields remainingThisMonth, overageThisMonth, quotaResetsAt, requestsPerMinute and account (company domain and billing status of a self-serve key). It no longer counts against the quota and answers at the quota too.
  • GET /v1/plans: new plan business (€499 a month); per-plan priceEurPerYear, selfServe, overageEligible; top-level annualDiscount (0.15), overage (the policy; hard_stop for now) and signup.
  • New response header x-quota-overage when soft overage is switched on (off by default).

1.2.0 · 2026-10-23

  • Companies: GET /v1/companies/{domain}/history (new): point-in-time hiring history since February 2023 from the Common Crawl archive, one row per job board per crawl (observedAt, availableAt, coverage flag) plus a per-crawl months summary; optional from / to.

1.1.0 · 2026-10-23

  • Tenders: GET /v1/tenders source adds national below-threshold sources boamp (FR), bund (DE), bzp (PL), eop (BG).

1.0.0 · 2026-10-23

First versioned release of the /v1 contract (snapshot openapi.v1.json):

  • Jobs: GET /v1/jobs, GET /v1/jobs/{id}.
  • Companies: GET /v1/companies/{domain}, GET /v1/company/{domain}, GET /v1/company/{domain}/signals, POST /v1/coverage, GET /v1/events.
  • Company Pulse: GET /v1/companies/{domain}/pulse, GET /v1/pulse/events.
  • Pay: GET /v1/pay/bands, GET /v1/pay/transparency.
  • Hotels: GET /v1/hotels/rate-curve, GET /v1/hotels/rate-curve/{city}.
  • Shops: GET /v1/shops/{domain}, GET /v1/shops/{domain}/apps.
  • Tenders: GET /v1/tenders.
  • Reviews: GET /v1/reviews/snapshot, GET /v1/reviews/panel.
  • Watchlists and account: /v1/watchlists, GET /v1/plans, GET /v1/me.
  • Deprecation / Sunset / Link headers on deprecated routes (none deprecated yet); /health reports the deployed release (git SHA) when the image sets one.