ContactPit

Data API

Developer documentation

Base URL, keys, endpoints, examples and errors for the ContactPit Data API.

Getting access

Access is set up by our team. It usually takes one short conversation.

  1. Contact us. Tell us what you are building, which countries you need and roughly how many companies you will read.
  2. Agree the terms. We agree the datasets, the request limit and how the data may be used and stored.
  3. Get your key. We send a server key with the scopes and registers you need. Keys have an expiry date and can be rotated at any time.
  4. Build and go live. Start with the capabilities call, then follow the examples below.

Quickstart

All endpoints live under one base URL. Send JSON, receive JSON. Start by asking what your key can use.

Base URL
https://api.contactpit.com/api/v2/partner
Format
JSON over HTTPS, UTF-8
Contract
2.0-draft
curl https://api.contactpit.com/api/v2/partner/capabilities \
  -H "Authorization: Bearer $CONTACTPIT_API_KEY"
{
  "data": {
    "registers": [
      { "country": "EE", "register": "ariregister", "domestic": true },
      { "country": "LV", "register": "lv_enterprise_register", "domestic": false }
    ],
    "features": {
      "companies": true, "search": true, "domain_candidates": true,
      "changes": true, "signals": false, "contacts": false, "research": false
    },
    "limits": {
      "page_size": 50, "company_batch_size": 1, "requests_per_minute": 120,
      "cursor_ttl_seconds": 86400, "journal_retention_seconds": null
    },
    "signal_types": [],
    "data_use_agreement_ref": "your-policy-id"
  },
  "meta": { "contract_version": "2.0-draft", "generation_id": "…", "published_at": "…", "request_id": "…", "rights_policy_id": "your-policy-id" }
}

Show features in your product only when capabilities says they are on. Limits that are null are not set for your key yet.

Authentication

Send your key in the Authorization header as a bearer token on every request. Keys are 32 to 256 URL-safe characters.

Authorization: Bearer <your-api-key>
  • Call the API from your server only. Never put the key in a browser, mobile app or public repository.
  • Each key carries scopes (for example companies:read or financials:read) and register grants (EE, LV). A call outside them returns 403.
  • Keys expire. Ask us for a new key before the old one runs out; both can work side by side while you switch.

Responses

Every successful answer has a data object and a meta object. meta tells you which data snapshot answered and carries a request ID for support.

{
  "data": { … },
  "meta": {
    "contract_version": "2.0-draft",
    "generation_id": "partner-snapshot-demo-7",
    "published_at": "2026-10-27T10:00:00Z",
    "request_id": "8a6f1c2e-…",
    "rights_policy_id": "your-policy-id"
  }
}

Responses are sent with Cache-Control: no-store and an X-Request-ID header. Log it: it is the fastest way for us to find a request.

Endpoints

Paths are relative to the base URL. The scope column is what your key needs.

MethodPathPurposeScope
GET/capabilitiesWhat your key can usecapabilities:read
GET/companies/{company_id}One company by ContactPit IDcompanies:read
POST/companies/resolveMatch a registry code or a namecompanies:read
POST/companies/domain-candidatesCompanies that list a website domaincompanies:read
GET/companies/filter-capabilitiesFilters available for one registercompanies:read
POST/companies/searchFiltered company search, pagedcompanies:read
GET/changesChange feed after a searchcompanies:read
GET/companies/{company_id}/financialsFinancial history, summary or headlinesfinancials:read
GET/companies/{company_id}/legal-rolesRegistry role recordslegal_roles:read
GET/companies/{company_id}/ownershipShareholders and beneficial ownersownership:read
GET/companies/{company_id}/holdingsCompanies this company holds shares inownership:read
GET/companies/{company_id}/officersBoard, council and other officersofficers:read
GET/companies/{company_id}/vatVAT registration statusvat:read
GET/companies/{company_id}/filingsAnnual report yearsfilings:read
GET/companies/{company_id}/business-profileBusiness description from the websitebusiness_profile:read
POST/signals/querySignals for up to 50 companiessignals:read

Contacts, research and construction project endpoints exist for pilot partners and are switched on per key. Capabilities shows whether they are on for you. See Company research and Construction projects.

Company record

company_id is ContactPit's permanent company ID (a UUID). It stays the same when a company changes its name or address, so store it as your key.

curl https://api.contactpit.com/api/v2/partner/companies/11111111-1111-4111-8111-111111111111 \
  -H "Authorization: Bearer $CONTACTPIT_API_KEY"
{
  "data": {
    "company_id": "11111111-1111-4111-8111-111111111111",
    "country": "EE",
    "register": "ariregister",
    "registry_code": "00123456",
    "revision": "company-r7",
    "entity_version": 7,
    "updated_at": "2026-10-27T10:00:00Z",
    "legal_name": "Example Demonstration OÜ",
    "status": "active",
    "deletion_date": null,
    "legal_form": "OÜ",
    "primary_activity": { "classification": "EMTAK", "version": "2025", "code": "62011" },
    "employees": { "kind": "period_average", "value": "4.5",
                   "period": { "start": "2025-01-01", "end": "2025-12-31" } },
    "website": { "listed_url": "https://example.com", "available": true },
    "linkedin": { "listed_url": "https://www.linkedin.com/company/example", "available": true },
    "location": { "city": "Tallinn", "region_code": null, "region_name": "Harju", "region_version": null },
    "sources": [
      { "source_id": "demo-register", "dataset": "synthetic_company_facts", "observed_at": "2026-10-27T10:00:00Z" }
    ],
    "availability": {
      "legal_name": { "state": "available", "reason": null, "source_refs": ["demo-register"] },
      "deletion_date": { "state": "not_applicable", "reason": "Company is active", "source_refs": [] },
      "…": "one entry per field"
    }
  },
  "meta": { … }
}

The example is synthetic. Status is one of active, deleted, in_liquidation or unknown. Numbers such as employees are exact decimal strings.

Keeping a copy fresh

Keep the sync_cursor from the first page of a search. After the last page, read /changes with it and store the new next_cursor after you apply each page.

GET /changes?cursor=signed-change-feed-cursor&limit=50

→ {
  "data": {
    "items": [
      {
        "event_id": "22222222-2222-4222-8222-222222222222",
        "type": "company_updated",
        "company_id": "11111111-1111-4111-8111-111111111111",
        "revision": "company-r7",
        "entity_version": 7,
        "replacement_ids": [],
        "occurred_at": "2026-10-27T10:00:00Z",
        "revocation": null
      }
    ],
    "next_cursor": "signed-change-feed-next",
    "has_more": false
  },
  "meta": { … }
}
  • Skip an event you have already applied (same event_id) and any event whose entity_version is not newer than the one you hold.
  • Ignore event types you do not know. New types can be added without a new contract version.
  • On rights_revoked, remove the ContactPit fields it names. Keep your own notes.
  • On 410 cursor_expired, start a new search snapshot and replace the ContactPit fields in your copy.
  • The feed is read by polling. Every few minutes is plenty.

Extra datasets

Extra datasets are separate reads by company ID. Each needs its own scope and a dataset agreement on our side. Until both are in place the endpoint returns 501 capability_unavailable or 403 scope_denied.

GET /companies/{company_id}/financials?section=history        # or summary, document_headlines
GET /companies/{company_id}/ownership?limit=20&current_only=true
GET /companies/{company_id}/officers?limit=20
GET /companies/{company_id}/vat
GET /companies/{company_id}/filings
GET /companies/{company_id}/business-profile

POST /signals/query
{ "company_ids": ["11111111-1111-4111-8111-111111111111"], "types": ["liquidation_started", "merger"], "since": "2026-01-01", "limit": 50 }
  • Financial amounts are exact decimal strings with their currency, period and scope. We do no currency conversion or growth maths for you.
  • Role and ownership records keep the register's own meaning. People are never identified by personal ID code; company holders link to their ContactPit ID.
  • Signal types: liquidation_started, bankruptcy_started, company_deleted, name_changed, capital_changed, merger, division, conversion, pledge_registered, pledge_ended, licence_granted, licence_ended, vat_registered, vat_deregistered, tax_debt_appeared, tax_debt_cleared and headcount_changed. A corrected signal comes back as retracted.

Errors and limits

Errors use one shape. retryable tells you whether trying again later can help.

{
  "error": {
    "code": "rate_limited",
    "message": "rate limited",
    "request_id": "8a6f1c2e-…",
    "retryable": true
  }
}
StatusCodeMeaning
400invalid_requestThe request body or parameters are not valid
401unauthorizedMissing, unknown or expired key
403scope_deniedYour key lacks the scope, register or dataset
404not_foundNo such company in your registers
410cursor_expiredThe cursor is too old; restart the paged read
422unsupported_filterThe register does not support this filter
429rate_limitedToo many requests; wait for Retry-After seconds
501capability_unavailableThe feature is not switched on for your key
503temporarily_unavailableTry again in 5 seconds
  • The default limit is 120 requests a minute per customer. Your limit is in capabilities.
  • On 429 wait the number of seconds in the Retry-After header. On 503 wait 5 seconds.
  • Request bodies can be at most 16 KB.

Data principles

  • Every fact carries its source and the date we observed it. Source dates are not live checks.
  • A missing value always comes with a reason. Unknown is never turned into false, zero or an empty list.
  • Registry codes are kept as exact text. Matching is by code; names and websites only give candidates.
  • Personal ID codes are never returned. Business contacts are only shared under a data-use agreement, and opt-outs reach your copy through the change feed.

Ready to build?

Tell us what you need and we will set up a key for it.