All documentsAPI

API reference

Authentication, rate limits, pagination, every endpoint and bulk exports.

Version 1. The client REST API gives programs the same company signals the Fokals pages show: what companies run on their own websites, how they hire and what their sales postings offer, what they are likely to buy next, their funding filings, what they announce about themselves, their headcount over time and their websites' traffic tier, with the identifiers a fund joins on (ticker and exchange, MIC, ISIN, LEI, FIGI, CIK, Wikidata id) and the equity listings behind them; and the market series computed weekly over all of it, how industries, countries, size bands and markets moved. Everything comes from public first-party sources, and only companies in the Fokals company index are returned.

Base URL: https://<your Fokals host>/api/v1. Every endpoint is a GET; responses are JSON unless an export asks for CSV or JSON lines.

Authentication

Every request carries an API key, issued by Fokals and shown once. Send it as a bearer token or in the X-Api-Key header:

GET /api/v1/companies?limit=50 HTTP/1.1
Authorization: Bearer fk_live_...

A key has one or both scopes: read for every endpoint except the exports, exports for /exports/.... A revoked key is refused at once (401 unauthorized); a key without the endpoint's scope gets 403 forbidden. Fokals stores only a hash of the key. Treat it as a password: keep it on your server and never in a page's source.

Rate limits

Each key has a number of requests a minute and a number of requests a day (UTC), set when it is made. Every response carries:

  • X-RateLimit-Remaining-Minute: requests left in the current minute.
  • X-RateLimit-Remaining-Day: requests left today.

Over either limit the answer is 429 rate_limited with a Retry-After header in seconds. Requests refused for the daily limit still count, so a client that keeps retrying stays refused until midnight UTC; back off instead.

Errors

A failed request has no data and one error:

{ "error": { "code": "not_found", "message": "No company with that id." } }
StatusCodeMeaning
400invalid_requestA parameter is missing, malformed or out of range, or a cursor does not fit the request. issues lists each problem.
401unauthorizedNo key, an unknown key or a revoked one.
403forbiddenThe key lacks the scope, or the product is not cleared for delivery.
404not_foundNo company, technology, product or dataset with that id.
429rate_limitedOver the minute or day limit; see Retry-After.
500server_errorSomething failed on our side; try again later.

Responses and pagination

A successful response is an envelope:

{ "data": [ ... ], "next_cursor": "eyJ1IjoiMjAyNi0..." }

data is the object or list asked for. Lists are paged by cursor: when next_cursor is not null, send it back as cursor with the same other parameters to get the next page; the last page has next_cursor: null. Cursors are opaque, tied to the query that made them, and can be kept and resumed later. limit sets the page size (1 to 200, default 100; exports 1 to 1000, default 500).

Feeds (/signals, /changes and their per-company forms) are ordered oldest first from since, so a client can walk forward and keep the last cursor as its bookmark. /news is the exception: newest first, so the latest announcements come at the top and the cursor walks back towards since.

Times, numbers and lists

  • Times are ISO 8601 in UTC (2026-09-22T10:11:12.123456Z); days are YYYY-MM-DD (UTC).
  • Numbers are JSON numbers; a value that is not known is null.
  • Lists in a row (topics, technologies, flags) are JSON arrays; detail, evidence, before, after and locations are JSON objects or arrays as stored.
  • Ids: companies are UUIDs and stable; technologies are short ids such as meta_pixel; signals and changes have integer ids.
  • Identifiers: identifiers on a company holds its listing's ticker, exchange (the venue code) and mic (ISO 10383), isin, lei, figi, and the company's cik and wikidata_id, each null when not known. Listings themselves are keyed by FIGI (/listed).
  • Pay is on one yearly US-dollar scale at the latest reference rate. Posting descriptions are never returned.

Coverage

Only websites written in English are read and returned. A website whose homepage is in another language is read in the English version it declares; without one it is in no answer, and a company with no website in English answers 404 not_found on every /companies/{id} endpoint and is in no list, feed, export, series or benchmark. A company returns, with what was collected before, when one of its websites is read in English. /listed keeps every listing; a listing of a company that is left out has company_id and company null.

CORS

Every endpoint answers OPTIONS and sends Access-Control-Allow-Origin: *, so a browser page could call the API. Because the key would then be visible to anyone who opens that page, call the API from your own server instead and hand your pages only what they need.

Endpoints

GET /api/v1/companies

Companies, latest change to the company record first (updated_at, then id). Filters combine with AND.

ParameterMeaning
qName or website contains this text.
industryIndustry label of the website (the values the Companies page offers).
modelBusiness model: b2b, b2c or both.
countryHeadquarters country code (US, DE, ...).
techRuns this technology now (technology id).
hiringactive: has new postings in the last 30 days.
topicIntent topic id that is the company's top topic or is surging.
listedyes: the company or its parent is listed.
isin, lei, figiIdentifier lookup: the company whose own record, or one of whose listings, carries this ISIN, LEI or FIGI. Case and spaces are ignored; a value that fails its shape or check digit is 400.
ticker, exchangeLookup by ticker (AAPL), narrowed to an exchange when one is given (a venue code such as US or NASDAQ, or a MIC such as XNAS); exchange alone lists the companies listed there.
updated_sinceCompany records updated at or after this date or time.
cursor, limitPaging.

Each row:

{
  "id": "5f0c...", "name": "Acme", "domain": "acme.com", "hq_country": "US",
  "ticker": "ACME", "exchange": "NASDAQ",
  "identifiers": { "ticker": "ACME", "exchange": "NASDAQ", "mic": "XNAS", "isin": "US0000000002", "lei": "54930000000000000002", "figi": "BBG000000007", "cik": "0000123456", "wikidata_id": "Q123456" },
  "industry": "software", "business_model": "b2b",
  "tech_count": 23, "open_postings": 41, "new_postings_30d": 9,
  "top_topic": "crm", "top_score": 71.5, "surge_topics": ["crm"],
  "last_change_at": "2026-09-20T03:12:44.101Z", "updated_at": "2026-09-01T00:00:00Z"
}

updated_at is when the company record (name, facts, listing) last changed, not when a signal arrived: poll /signals and /changes for new activity.

GET /api/v1/companies/{id}

One company: the record, its website's labels and the AI-written briefs.

Fields: id, name, status (public, subsidiary, private, unknown), sector, industry_wikidata, hq_country, founded_year, employees, employees_as_of, domains, ticker, exchange, listed_company, listed_company_id, identifiers, domain, last_status, last_checked_at, traffic, headcount_change, summary, labels, briefs.

traffic is the website's latest Chrome UX Report tier: { bucket, month, trend }, where bucket is the rank bucket (see /companies/{id}/traffic; null when no site is ranked) and trend is up, down, flat or null against three months earlier. headcount_change is { from, to, from_as_of, to_as_of } from the last two headcount points, or null with fewer than two.

identifiers is { ticker, exchange, mic, isin, lei, figi, cik, wikidata_id }: the listing's identifiers (the company's own, or its listed owner's when listed_company_id is another company), with lei the company's own LEI when it has one, and its CIK and Wikidata id. Each is null when not known.

summary (null before the first hourly summary): industry, business_model, offering, sales_motion, price_tier, flags, tech_count, open_postings, new_postings_30d, top_topic, top_score, surge_topics, last_change_at, last_read_at, filings_12m.

labels is the website evaluation as stored: version (sites-v1), industry, business_model, offering, sales_motion, price_tier, target_customer_size, pricing_model, geography_scope, primary_persona, growth_stage, the 0 to 3 levels ecommerce_maturity, tech_sophistication and content_intensity, and the flags (sells_online, physical_locations, subscription, ai_product, sustainability, enterprise_focus, international, has_api, has_mobile_app, has_free_trial, has_demo_request, compliance_badges, has_partner_program, sells_to_government, regulated_industry), without the model's probabilities; null before the site is labelled.

briefs is a list, one per kind:

{ "kind": "brief", "version": "...", "model": "...", "text": "...", "evidence": { }, "generated_at": "2026-09-21T05:00:00Z", "ai_text": true }

Kinds: brief (what the company is and does), why_now (why it may be buying now), monthly_change (what changed in the last month). See AI-written text below.

GET /api/v1/companies/{id}/stack

What the company's website runs and shows, from its latest reading; null when no website is on file.

Fields: domain, checked_at, label_status, groups (a list of { category, technologies }, each technology { id, name, category, catalogue, first, last, missing, via, ids }), markets, languages, currencies (lists of { key, first, last, missing }), social ({ name, url }), apps ({ ios, android }), pages ({ name, url }), schemaTypes, title, description, promo, dns (technologies seen in DNS records), platforms ({ key, value }).

first and last are the first and latest reading that saw the item; missing is set while it is absent but not yet counted as removed; via says how it was seen (html, container, network, header, dns); ids are the public account ids found with it. catalogue says whose signature recognised the technology: fokals for the list Fokals writes and tests, open for the open-source catalogue (Methodology, section 5.1).

GET /api/v1/companies/{id}/hiring

Open postings and daily hiring for the last 90 days.

Fields: open, by_function, by_seniority, by_country, by_work_mode (lists of { key, count } over open postings), daily ({ day, open_postings, new_postings, closed_postings, median_salary_usd }, newest first), recent (up to 50 open postings, newest first: id, title, url, department, country, locations, work_mode, posted_at, first_seen_at, salary_usd_annual_min, salary_usd_annual_max, job_function, seniority, technologies).

GET /api/v1/companies/{id}/postings

The company's postings opened or closed from since on, oldest first. Parameters: since, event, function, seniority, country, baseline, cursor, limit. Rows as in /postings below.

GET /api/v1/companies/{id}/sales

The company's sales organisation, read from its own sales postings: the open sales postings now, the last twelve closed weeks, and the open postings with what each advertises. A sales posting is one in a sales function whose role and segment were read under sales-v1 (a posting the reading found to be no sales role, other, is not counted); its stated facts (OTE, base, quota, ramp, lead source) are extracted from the posting text. Every figure is what the employer advertises, never what anyone is paid.

{ "open_sales": 14, "open_all": 120,
  "by_role": [ { "key": "account_executive", "count": 8 }, { "key": "sdr_bdr", "count": 4 } ],
  "by_segment": [ { "key": "enterprise", "count": 6 }, { "key": "mid_market", "count": 5 } ],
  "by_country": [ { "key": "US", "count": 10 }, { "key": "DE", "count": 4 } ],
  "median_base_usd": { "account_executive": 95000, "sdr_bdr": 55000 }, "median_ote_usd": { "account_executive": 190000 },
  "median_base_usd_all": 80000, "median_ote_usd_all": 190000, "tools": { "salesforce": 9, "gong": 3 },
  "weekly": [ { "week_start": "2026-09-14", "open_sales": 14, "new_sales": 3, "closed_sales": 1, "open_all": 120,
               "by_role": { "account_executive": 8, "sdr_bdr": 4 }, "by_segment": { "enterprise": 6 }, "by_country": { "US": 10 },
               "new_countries": ["DE"], "upmarket": false, "median_base_usd": { "account_executive": 95000 }, "median_ote_usd": { },
               "tools": { "salesforce": 9 }, "quota_stated": 5, "inbound_share": 40, "reconstructed": false } ],
  "recent": [ { "id": "7c2a...", "title": "Enterprise Account Executive", "url": "https://acme.com/jobs/123", "country": "US", "work_mode": "remote",
                "first_seen_at": "2026-09-16T10:00:00Z", "role": "account_executive", "segment": "enterprise", "base_usd": 100000, "ote_usd": 200000,
                "quota_usd": 1200000, "ramp_months": 3, "lead_source": "mixed", "uncapped": true, "technologies": ["salesforce", "gong"] } ] }

Roles: sdr_bdr, account_executive, account_manager, sales_engineer, sales_leadership, sales_operations, partnerships_channel, other. Segments: smb, mid_market, enterprise, unspecified. by_role, by_segment and by_country are over the open sales postings; open_all is every open posting, for the sales share. median_base_usd and median_ote_usd are by role over the open sales postings that state the figure, median_base_usd_all and median_ote_usd_all over all of them; a posting's base is the base it states, or the midpoint of a plain salary range when it names no OTE (a sales posting that names only a range is offering it as the base). tools counts open sales postings naming each sales tool (salesforce, hubspot, outreach, gong, ...).

weekly has the last twelve closed weeks (Monday to Sunday, UTC), newest first, each written once at the week's end: new_countries are countries with a first sales posting that week and none before, upmarket marks the first enterprise-segment sales posting ever, quota_stated counts open sales postings that state a quota, inbound_share is the median stated share of inbound leads (percent). recent has up to 30 open sales postings, newest first: quota_usd and ramp_months as stated, lead_source inbound, outbound or mixed, uncapped when the posting says commission is uncapped.

GET /api/v1/companies/{id}/intent

Intent scores by topic for the last twelve closed weeks and the signals of the last 90 days.

Fields: latest_week (the Monday of the latest scored week), latest (that week's rows), weeks (every row, newest week first, highest score first), signals (up to 200, newest first).

A week row: { week_start, topic, score, surge, signals, evidence }. score is 0 to 100 from the signals of the 90 days to the week's end, each fading by half every 30 days; surge is true at 50 or more and at least double the company's average of the twelve weeks before; evidence lists the five strongest signals. A signal row is as in /signals below, without company_id and company.

GET /api/v1/companies/{id}/signals

The company's signals from since on, oldest first. Parameters: since (ISO date or time; default seven days ago), source (site, dns, careers, filing, news), topic, cursor, limit. Rows as in /signals below.

GET /api/v1/companies/{id}/changes

Changes seen on the company's website from since on, oldest first. Parameters: since, category, cursor, limit. Rows as in /changes below.

GET /api/v1/companies/{id}/filings

SEC Form D filings matched to the company, newest first, up to 200.

Row: accession, form, issuer, filed_at, industry, is_fund, city, state, amount_offered, amount_sold, first_sale, is_amendment, people (always an empty list: the people a filing names, its officers, directors and promoters, are not read).

GET /api/v1/companies/{id}/news

What the company has announced about itself, newest first, in its own words: items from the feed it declares or its newsroom page and, for a listed company, its Form 8-K filings. Parameter: limit (1 to 200, default 50). Not paged.

{ "items": [ {
  "id": "9b1e...", "source": "page", "url": "https://acme.com/news/series-b", "title": "Acme raises $40M Series B",
  "at": "2026-06-12T09:00:00Z", "published_at": "2026-06-12T09:00:00Z", "first_seen_at": "2026-06-12T11:40:21.510Z",
  "excerpt": "Acme today announced ...", "accession": null, "items": [], "event_types": ["funding_round"], "label_version": "news-v1",
  "roles": [], "role_labels": null
} ] }

source is rss, atom or page (the company's feed or newsroom) or sec_8k; at is published_at, or first_seen_at when the item carries no date. excerpt is at most 1,200 characters of the announcement's own text. event_types are from a fixed list (product_launch, partnership, acquisition_made, acquired_or_merged, funding_round, leadership_change, layoffs_restructuring, expansion, incident, financial_results, award_or_recognition, regulatory_or_legal, other). For an 8-K, accession and items (the form's item numbers, such as 5.02) are set and label_version is sec-items-v1; on other items it is news-v1 (see AI-written text below). roles names the leadership roles behind a leadership_change (ceo, cfo, coo, cro_cso, cmo, cto_cio_cpo, chro, general_counsel, board, other_executive), read under roles-v1 from the announcement's own text; it is empty on other items and never names the person. role_labels is that reading as stored ({ version, role, direction, multiple, probabilities }, with direction appointed, departed or succession), or null before it is made.

GET /api/v1/companies/{id}/headcount

The company's headcount over time, oldest first, from its annual reports and Wikidata.

{ "points": [ { "as_of": "2024-12-31", "employees": 1200, "source": "sec_10k", "ref": "0001-25-000123" },
              { "as_of": "2025-12-31", "employees": 1450, "source": "sec_10k", "ref": "0001-26-000117" } ] }

source is sec_10k (ref is the filing's accession) or wikidata (ref is the item). One point per date: when both name the same day, the annual report's count is kept.

GET /api/v1/companies/{id}/traffic

The monthly traffic tier of the company's websites for the last 13 months, from the Chrome UX Report: the smallest popularity bucket (1,000 / 5,000 / 10,000 / 50,000 / 100,000 / 500,000 / 1,000,000 / 5,000,000 / 10,000,000 / 50,000,000 origins) the site was in that month. A smaller bucket is more traffic; no visitor data is involved.

{ "months": [ { "month": "2026-08-01", "rank_bucket": 10000, "domain": "acme.com" }, { "month": "2026-07-01", "rank_bucket": 50000, "domain": "acme.com" } ],
  "latest": { "bucket": 10000, "month": "2026-08-01", "domain": "acme.com" }, "trend": "up",
  "attribution": "Chrome UX Report (Google), CC BY 4.0" }

months is newest first; latest is the best bucket of the latest month (null with no ranked site); trend compares that site with its bucket three months earlier (up, down, flat, or null without one). The data is Google's under CC BY 4.0: show attribution wherever you show a tier.

GET /api/v1/listed

Listed securities: every equity listing known to Fokals, one row per FIGI, ordered by FIGI, with the company it belongs to when one matches. Parameters: exchange (OpenFIGI's venue code such as US or LN, or a MIC such as XNAS), country (the issuer's country code, from its LEI record), status (active or delisted), cursor, limit.

{ "figi": "BBG000B9XRY4", "share_class_figi": "BBG001S5N8V8", "name": "APPLE INC", "ticker": "AAPL", "exchange": "US", "mic": "XNAS",
  "isin": "US0378331005", "lei": "HWUPKR0MPOU8FGXBT394", "country": "US", "security_type": "Common Stock", "status": "active",
  "company_id": "5f0c...", "company": "Apple" }

company_id and company are null for a listing not yet tied to a company, or tied to one that is left out (Coverage). The FIGI, ticker, exchange and MIC come from OpenFIGI, the LEI and country from GLEIF (through the ISIN) or Wikidata, tickers also from Wikidata; a listing Wikidata asserts and OpenFIGI does not yet carries a wd: key as its figi until a pass brings the FIGI. status is delisted once the listing has not been seen for 45 days after a full pass; the row is kept.

GET /api/v1/sales/benchmarks

Advertised sales pay per role and country for one closed week, across every company's open sales postings: the quartiles of the stated base and of the stated OTE, in yearly US dollars. Parameters: role (a role id as under /companies/{id}/sales), country (a two-letter country code, or ALL for every country together), week (the week's Monday as YYYY-MM-DD; default the latest week with benchmarks), limit (1 to 200, default 100). Rows by role, then country. Not paged.

{ "week_start": "2026-09-14", "role": "account_executive", "country": "US", "postings": 212,
  "base_p25_usd": 80000, "base_median_usd": 100000, "base_p75_usd": 125000,
  "ote_p25_usd": 160000, "ote_median_usd": 200000, "ote_p75_usd": 250000 }

postings is the number of open sales postings in the group that state a base or an OTE; a quartile is null when none in the group states that figure. Only groups of five or more postings are published, so no single posting can be read back out of a quartile. The base follows the rule under /companies/{id}/sales. The pay is advertised, not realised: it says what employers offer sales roles, not what anyone earns.

GET /api/v1/technologies

Every technology recognised on company websites, by the number of companies running it: { id, name, category, catalogue, scored, established_at, companies, added_30d, removed_30d, net_30d }. catalogue is fokals or open, as under /companies/{id}/stack. scored says whether the technology's changes count for intent and it has weekly series: always for fokals, and for open from established_at, the time it was first counted on 100 companies (null until then, and for fokals). added_30d and removed_30d count the websites already watched that started or stopped running it in the last 30 days, and net_30d is the first less the second; a company new to the index counts in companies, not as an addition. Not paged.

GET /api/v1/technologies/{id}

One technology and the companies running it now, by name; cursor and limit page the companies.

{ "id": "meta_pixel", "name": "Meta Pixel", "category": "advertising", "catalogue": "fokals", "topics": ["paid_social_ads"],
  "adopters": [ { "id": "...", "name": "Acme", "domain": "acme.com", "industry": "software", "hiring_active": true, "ticker": null } ] }

GET /api/v1/signals

Every dated buying signal across companies from since on, oldest first. Parameters: since (default seven days ago), source (site, dns, careers, filing, news), topic (intent topic id), cursor, limit.

{ "id": 48213, "observed_at": "2026-09-22T10:11:12.123456Z", "source": "site", "kind": "tech_added",
  "topics": ["crm"], "weight": 2, "detail": { "technology": "hubspot" }, "company_id": "...", "company": "Acme" }

kind names what was seen (tech_added, tech_removed, tech_account_added, replatformed, market_added, language_added, currency_added, app_added, social_account_added, sales_motion_added, careers_added, posting_tool, funding, news_event and more); weight is the signal's strength before fading; detail is what was seen. A news_event (source news) is an announcement: its detail is { title, url, events, from }, with events the event types and from the announcement's source.

GET /api/v1/news

Every announcement across companies from since on, newest first. Parameters: since (default seven days ago), event_type (one of the event types listed under /companies/{id}/news), source (rss, atom, page, sec_8k), cursor, limit.

{ "id": "9b1e...", "company_id": "5f0c...", "company": "Acme", "at": "2026-06-12T09:00:00Z",
  "title": "Acme raises $40M Series B", "url": "https://acme.com/news/series-b", "source": "page",
  "event_types": ["funding_round"], "items": [], "accession": null, "label_version": "news-v1", "roles": [], "role_labels": null }

Rows carry no excerpt; /companies/{id}/news has it and explains roles and role_labels.

GET /api/v1/changes

Every change seen on a company website from since on, oldest first. Parameters: since, category (technology, technology_id, market, language, currency, social, app, page, promo, dns, platform), cursor, limit.

{ "id": 91820, "domain": "acme.com", "observed_at": "2026-09-22T10:11:12.123456Z", "category": "technology",
  "key": "hubspot", "change": "added", "before": null, "after": { "via": ["html"] }, "company_id": "...", "company": "Acme" }

change is added, removed or changed. A site's first reading sets its baseline and writes no changes.

GET /api/v1/postings

Every job posting opened or closed from since on, across companies, oldest first: the hiring product as a stream a client can poll. Parameters: since (default seven days ago), event (opened or closed), function (a jobs-v2 job function id, as labels.job_function on the rows), seniority (a jobs-v2 seniority id, as labels.seniority), country (ISO 3166-1 alpha-2), baseline (true to include postings found on a board's first read, which were open before we saw them and are not new; left out by default), cursor, limit.

{ "id": "7c1e...", "event": "opened", "at": "2026-09-22T10:11:12.123456Z", "company_id": "5f0c...", "company": "Acme",
  "title": "Account Executive, Mid-Market", "url": "https://boards.greenhouse.io/acme/jobs/123", "department": "Sales",
  "country": "US", "locations": [{ "city": "Austin", "country": "US" }], "work_mode": "remote", "employment_type": "full_time",
  "posted_at": "2026-09-21T00:00:00Z", "first_seen_at": "2026-09-22T10:11:12.123456Z", "closed_at": null, "baseline": false,
  "salary_usd_annual_min": 90000, "salary_usd_annual_max": 120000,
  "labels": { "job_function": "sales", "seniority": "mid", "remote_policy": "remote", "tool_salesforce": true },
  "sales_labels": { "role": "account_executive", "segment": "mid_market", "quota_stated": true } }

event is opened (at is first_seen_at) or closed (at is closed_at, when the posting left the board); a posting is in the feed once for each, over its life, and the row carries the posting as it stands now. labels and sales_labels are null until the posting is labelled under the current versions (jobs-v2, sales-v1), and sales_labels is null for a posting that is not a sales role. Descriptions are never returned.

Market series

How an industry, a country, a size band or a market moved, computed every week from the company tables and written once per as-of date, a Sunday (UTC) on which the windows end. A series is one metric over one dimension and one window (7, 30 or 180 days; 365 for headcount; 30 and 180 for traffic and listings); a row is that series at one as-of date, with:

  • count: the numerator, events, postings or companies in the window; for a median metric, the median itself.
  • cohort: what the rate is over. Rates are same-store: the cohort is the companies or websites covered since before the window began (postings opened in the window for share and pay metrics), so a growing company index is not mistaken for a growing market.
  • rate: count in the metric's unit: per_100_companies and per_100_sites (per 100 of the cohort), share_of_postings and share_of_companies (0 to 1), median_usd (yearly US dollars, advertised), median_pct (percent); null for count metrics (listings).
  • previous: the same count over the window just before this one, on the same cohort; growth: (count - previous) / previous, null when there was nothing before.
  • index: 100 at the series' first as-of date (the first series is as of 2026-09-27), then the rate relative to it.

A row over fewer than 20 companies (20 postings for pay and share metrics) is not written: a small cohort says more about coverage than about a market. Rows are never changed once written. metric is a family id (hiring_new, pay_median, tech_added) or, for a family with subjects, family:subject (tech_added:hubspot, seniority_share:senior, news_event:funding_round, intent_surge:crm); /series/metrics lists them all. Dimensions: all (the whole index), industry (the website's industry id), sector (the company's sector), country (headquarters, ISO code), size (1-10, 11-50, 51-200, 201-1000, 1001-5000, 5000+ employees) and market (an exchange code, listings only).

GET /api/v1/series

Series rows, newest as-of date first, then by metric, dimension_kind, dimension and window_days. Filters combine with AND.

ParameterMeaning
metricOne metric: a family (hiring_new) or a family and subject (tech_added:hubspot). A name whose family is not in the catalogue is 400.
familyEvery metric of a family, subjects included (tech_added). An unknown family is 400.
dimension_kindall, industry, sector, country, size or market.
dimensionOne dimension: an industry id, a sector, a country code, a size band, an exchange code, or all.
window7, 30, 180 or 365 days.
since, untilAs-of dates as YYYY-MM-DD, both included.
cursor, limitPaging; limit up to 1000, default 200.

Each row:

{ "as_of": "2026-10-04", "metric": "tech_added:hubspot", "family": "tech_added", "subject": "hubspot", "unit": "per_100_sites",
  "dimension_kind": "industry", "dimension": "software_saas", "window_days": 30,
  "count": 14, "cohort": 620, "rate": 2.2581, "previous": 9, "growth": 0.5556, "index": 128.4 }

family, subject and unit are read from the catalogue; subject is null on a family without subjects.

GET /api/v1/series/metrics

The catalogue: every metric family with id, name, about, source (hiring, sales, technology, site, news, intent, traffic, headcount, listings), unit, windows, dimensions, cohort (who the rate is over, in words), min_cohort, and for a family with subjects its subject_kind and subjects ({ id, name }). Technology subjects are the technologies that have a series (the whole technology catalogue before the first series is written). Not paged.

{ "id": "seniority_share", "name": "Seniority share", "about": "Share of postings opened in the window at each seniority level (jobs-v2).",
  "source": "hiring", "unit": "share_of_postings", "windows": [7, 30, 180], "dimensions": ["all", "industry", "sector", "country", "size"],
  "cohort": "postings opened in the window with a seniority label", "min_cohort": 20,
  "subject_kind": "seniority", "subjects": [ { "id": "intern", "name": "Intern" }, { "id": "senior", "name": "Senior" } ] }

GET /api/v1/series/movers

The series that moved most at one as-of date: the rows of one dimension kind and window ordered by absolute growth, largest first. Parameters: as_of (YYYY-MM-DD; default the latest written), window (default 30), dimension_kind (default industry), family (optional; an unknown family is 400), limit (1 to 500, default 50). Rows with no growth (nothing in the previous window) are left out. Rows as in /series. Not paged.

{ "as_of": "2026-10-04", "metric": "hiring_new", "family": "hiring_new", "subject": null, "unit": "per_100_companies",
  "dimension_kind": "industry", "dimension": "gaming", "window_days": 30,
  "count": 96, "cohort": 240, "rate": 40, "previous": 61, "growth": 0.5738, "index": 131.1 }

A large growth on a small count is a small market moving a little: read count and cohort with it.

Exports

GET /api/v1/exports/{product}/{dataset}

The delivery datasets, page by page, for a period. Needs the exports scope; a product whose source is not cleared for delivery answers 403.

ParameterMeaning
from, toThe period as YYYY-MM-DD (UTC days, both included). Required.
formatjson (default: the envelope above), jsonl (one JSON object a line, application/x-ndjson) or csv (text/csv, UTF-8, one header row, lists and objects as JSON in a cell).
cursor, limitPaging; limit up to 1000, default 500.

The next page's cursor is in the X-Next-Cursor header on every format (absent on the last page) and also in next_cursor for json. Rows are ordered by the dataset's keys below, so a period can be walked page by page and resumed.

ProductDatasetPage order (keys)
marketing_stackcompany_technologiesdomain, technology
marketing_stackcompany_tech_eventsobserved_at, domain, category, key, change
marketing_stackcompany_site_factsdomain
hiringjob_postingsposting_id
hiringcompany_hiring_dailyday, company_id
hiringcompany_sales_weeklyweek_start, company_id
hiringsales_pay_benchmarksweek_start, role, country
intentcompany_intent_weeklyweek_start, company_id, topic
intentcompany_signalsobserved_at, company_id, kind
intentcompany_fundingaccession
newscompany_newsat, company_id, url
newscompany_headcountsas_of, company_id, source
newstraffic_ranksmonth, domain
marketmarket_seriesas_of, metric, dimension_kind, dimension, window_days

The news product ("Announcements and scale") holds the announcements, the headcount points and the monthly traffic tiers; every traffic_ranks row carries an attribution column (see Licence).

The market product ("Market series") holds the weekly market series (see Market series above): the rows whose as_of falls in the period, with family, subject, metric_name, unit and source added from the catalogue and written_at when the row was written. It names no company, so it carries no identifiers.

In the hiring product, every job_postings row carries sales_facts (what a sales posting states about the deal: OTE, base, split, quota, ramp, lead source, deal size, cycle; null when it states none) and sales_labels (its role, segment and flags under sales-v1; null outside a sales function). company_sales_weekly is the weekly picture behind /companies/{id}/sales, one row per company and closed week, and sales_pay_benchmarks the rows behind /sales/benchmarks, one per week, role and country. The pay in both is advertised, not realised.

Every dataset row that names a company also carries its listing's ticker, exchange, isin, lei, figi and mic (null when the company is not listed), so rows join to holdings by whichever identifier a system uses.

What each dataset holds and what its columns mean is in the field guide delivered with every export (company-signals/DATA_DICTIONARY.md), with the method in METHODOLOGY.md and the sourcing in SOURCING.md.

AI-written text

The briefs on /companies/{id} are written by a language model from the company's own public signals. Each carries "ai_text": true, its model and version, and the evidence it was written from. Treat a brief as a reading of the evidence, not as a fact about the company: check the evidence before acting on it, and mark it as AI-written wherever you show it. Every other field is collected, counted or scored, not generated.

The event_types on newsroom and feed announcements (/news, /companies/{id}/news, the company_news dataset) are read by a model under a named label version (news-v1) from the announcement's own text; the title, date, link and excerpt are the company's. On a Form 8-K the event types are mapped from the form's item numbers (sec-items-v1), with no model involved.

The role and segment of a sales posting (/companies/{id}/sales, the sales_labels column) are read by a model under sales-v1, and the leadership roles on an announcement under roles-v1; the facts a sales posting states (OTE, base, quota, ramp) are extracted from its text. Each is a reading of the posting or announcement, not a fact about the company, and the pay figures are what is advertised.

Licence

Licensed under your written agreement with Fokals. Where there is none, under the Fokals Terms of Service (/terms): internal business use only; no resale or redistribution; not for investment decisions.

Traffic tiers (traffic on a company, /companies/{id}/traffic, the traffic_ranks dataset) are derived from the Chrome UX Report by Google, licensed under CC BY 4.0. Keep the attribution "Chrome UX Report (Google), CC BY 4.0" wherever you show or pass on a tier.

Scores describe public signals. They are our assessment, not statements about any company's plans, and not forecasts.

Versioning

The path carries the version (/api/v1). Within a version, fields and endpoints are added without notice, so clients should ignore fields they do not know; nothing documented here is renamed, removed or given a different meaning. A change that would break a client goes into a new version, and the old one stays for at least six months after the new one is announced here. Label versions (sites-v2, jobs-v2, intent-v1, news-v1, sec-items-v1, sales-v1, roles-v1) travel with the rows they describe.