REST API

Search for companies with a single HTTP request. Base URL: https://trycanonical.ai

Authentication

All API requests require an API key passed in the x-api-key header. Create keys from your Settings.

curl https://trycanonical.ai/api/v1/search?q=fintech \
  -H "x-api-key: YOUR_API_KEY"

Keys start with sk_. Keep them secret — they grant full API access to your account.

Search Companies

GET POST /api/v1/search

Supports two modes. Exactly one must be used per request — passing both, or neither, returns 422.

  • NL mode (GET or POST): pass ?q= as a query parameter. The server runs the NL interpreter, then searches. Response includes interpreted_criteria + unmapped + warnings.
  • Structured mode (POST, JSON body only): pass a JSON body with description and/or filters — the interpreter is skipped entirely. Response includes warnings but no interpreted_criteria. Note: structured mode is batch JSON only — sending Accept: text/event-stream with a structured body returns 406.

NL Mode — Query Parameters

Parameter Type Default Description
q string required Natural language search query
top_k integer 20 Number of results (1–1000)
intent string inferred Optional ranking profile applied after relevance filtering. Pass it when your client (e.g. an agent reading the user's goal from chat or memory) already knows the right slug; omit it to let the server infer intent from the query text. Allowed slugs: sales_prospecting, sales_timing, sales_expansion, competitive_tracking, emerging_competitor_scan, talent_source, recruiter_employer_vet, jobseeker_stability, jobseeker_growth.
include_partials boolean false When true, also returns LLM-evaluated "partial" matches (close but not strong). Off by default because partials aren't billed. Behavior change: previously partials were included; opt back in here.
exclude_company_domain string (repeatable) Repeat to exclude multiple companies by domain (e.g. ?exclude_company_domain=stripe.com&exclude_company_domain=square.com). Stacks with any exclusions named in the NL query. Unresolvable domains appear in warnings.

Breaking changes in the latest release: id has been removed from the result rows — domain is now the public company identifier. The include_partials parameter defaults to false (previously partials were always returned). If your integration used id as a key, switch to domain.

Structured Mode — Request Body

POST a JSON body instead of ?q= to skip NL interpretation. The interpreter is not called — filters are applied exactly as supplied.

Field Type Description
description string | null Free-text semantic core. Drives ColBERT retrieval + LLM verification. Omit for filter-only searches.
filters object | null Structured filter object. All keys are optional. Supported keys:

location (object) — nested location filter with keys cities, states, countries (each a list[string]).
employee_size (string) — headcount bucket (e.g. "51-100").
founding_year_min / founding_year_max (integer) — inclusive bounds on the company's founding year (e.g. {"founding_year_min": 2015, "founding_year_max": 2020}). For an exact year, set both to the same value.
founding_year_exclude_min / founding_year_exclude_max (integer) — inclusive bounds of a contiguous range to exclude. Both must be set together; for a single-year exclusion, set both to the same year. NULL-inclusive (companies with unknown founding year survive exclusion).
funding_series (list[string]) — stage slugs (e.g. ["series_a", "series_b"]).
funding_min_usd (integer) — minimum total funding raised.
funding_post_money_min_usd (integer) — minimum post-money valuation.
funded_after (string) — ISO date; only companies funded on or after this date.
funding_investor (list[string]) — investor firm name variants.
people (object) — nested people filter with keys: has_repeat_founder (boolean), has_technical_cofounder (boolean), requires_role_types (list[string]), founder_prior_categories (list[string]), founder_prior_companies (list[string]).
exclude_company_domains (list[string]) — domains to exclude from results.
exclude (object) — nested exclusion filter with keys: description (string, semantic exclusion), location (object with cities/states/countries), funding_series (list[string]), funding_investor (list[string]).
intent string | null Optional ranking profile slug (same values as NL mode). Omit to skip re-ranking.
top_k integer Number of results (1–1000, default 20).
include_partials boolean Include partial matches. Default false.

Validation rules: exactly one of ?q= (NL mode) or a JSON body (structured mode) must be present per request — both → 422, neither → 422. Streaming (Accept: text/event-stream) is supported for NL mode only; sending it with a structured body returns 406.

Structured Mode — Example

curl -X POST "https://trycanonical.ai/api/v1/search" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "B2B fintech",
    "filters": {
      "location": {"cities": ["New York City"], "countries": ["United States"]},
      "employee_size": "51-100",
      "founding_year_min": 2018,
      "founding_year_max": 2022,
      "funding_series": ["series_a", "series_b"],
      "funding_min_usd": 100000000,
      "people": {"has_technical_cofounder": true, "founder_prior_categories": ["faang"]},
      "exclude_company_domains": ["stripe.com"],
      "exclude": {"description": "consulting", "location": {"countries": ["India"]}}
    },
    "intent": "sales_prospecting",
    "top_k": 20
  }'

# Response — same shape as NL mode, but no interpreted_criteria field
{
  "results": [ /* ... */ ],
  "count": 5,
  "credits_used": 5,
  "credits_remaining": 995,
  "warnings": []
}

Intent slugs

Each slug controls how the verified result set is reordered. The ranker uses precomputed signals (hiring direction, momentum, retention, talent magnetism, brand reach) so picking an intent doesn't run a second search — it just changes the sort. The same slugs also reorder similar-to results: pass intent to /api/v1/similar, or use it on a /api/v1/search "similar to X" query.

Slug When to pick it What the ranker optimizes for
sales_prospecting Top-of-funnel outbound list with no specific buying-window constraint. Generic ICP fit. Companies actively growing, hiring, with brand reach.
sales_timing "Who is buying now?" — near-term buying window. Recent hiring acceleration paired with healthy growth.
sales_expansion Existing customers ripe for upsell, cross-sell, or deepening. Stable, steadily-growing companies with strong retention.
competitive_tracking Monitoring named competitors' direction and posture. Hiring-direction shifts, momentum, brand reach, geo footprint.
emerging_competitor_scan Discovering new entrants / rising challengers. Fast-growing, aggressively hiring companies with rising visibility.
talent_source Recruiter / hiring manager looking for companies to poach from. Hiring in target function, alumni footprint, team pedigree, retention.
recruiter_employer_vet Evaluating whether to send a candidate to a particular company. Employer retention, stability, talent demand, brand reach.
jobseeker_stability Job seeker prioritizing job security ("is this company safe?"). Low churn, positive net flow, retention, moderate momentum.
jobseeker_growth Job seeker prioritizing career upside ("where can my career accelerate?"). High momentum, growth-function hiring, talent magnetism, hiring velocity.

Example Request

curl "https://trycanonical.ai/api/v1/search?q=AI+healthcare+startups&top_k=5" \
  -H "x-api-key: YOUR_API_KEY"

# Intent-ranked (sales prospecting):
curl "https://trycanonical.ai/api/v1/search?q=AI+healthcare+startups&top_k=20&intent=sales_prospecting" \
  -H "x-api-key: YOUR_API_KEY"

Response

{
  "results": [
    {
      "name": "Acme Health AI",
      "domain": "acmehealthai.com",
      "description": "AI-powered diagnostic platform...",
      "headquarters": "San Francisco, CA, United States",
      "employee_count": 150,
      "founding_year": 2019,
      "funding": { "latest_series": "series_b", "latest_amount_usd": 45000000 },
      "dimensions": {
        "industry": ["Healthcare", "Artificial Intelligence"],
        "offering_type": ["Software"]
      },
      "verdict": "relevant",
      "verdict_reason": "AI diagnostic platform matches the healthcare/AI criteria..."
    }
  ],
  "count": 5,
  "query": "AI healthcare startups",
  "credits_used": 5,
  "credits_remaining": 995,
  "warnings": [],
  "interpreted_criteria": {
    "description": "AI healthcare startups",
    "intent_inferred": "sales_prospecting"
  }
}

Response Fields

Field Type Description
results Company[] Array of matching companies (strong verdicts only by default; partials when include_partials=true).
results[].name string Company name
results[].domain string Primary domain. This is the public company identifier — use it for follow-up calls (exclusions, details, similar).
results[].description string Company description
results[].headquarters string | null Company headquarters location
results[].employee_count integer | null Number of employees
results[].founding_year integer | null Year the company was founded. null when unknown.
results[].funding object | null Latest funding round: {latest_series, latest_amount_usd, latest_lead_investor, latest_date}. Null when no funding data.
results[].dimensions object | null Classification tags (industry, offering_type, target_customer, etc.)
results[].verdict string | null "relevant" (strong match) or "partial" (only with include_partials=true).
results[].verdict_reason string | null LLM verifier's reasoning (truncated for compactness). Useful for explainability.
count integer Number of results returned
query string Echo of the original query
credits_used integer Credits consumed (1 credit per strong result row)
credits_remaining integer | null Remaining credits for your account
warnings object[] Filter-degradation signals when an exclusion couldn't be fully resolved. Each entry: {code, message, field, attempted}. Empty when the search ran cleanly.
interpreted_criteria object | null How the natural-language query was parsed (description, location, employee_size, funding axes, exclusions, inferred intent). Use this for transparency — if interpretation looks wrong, retry with a clearer query. See POST /api/v1/interpret for preview-before-search.

Preview Interpretation

Preview how a natural-language query parses into structured criteria — call this BEFORE /api/v1/search when you want to confirm the system's understanding without spending credits. Free, rate-limited.

POST /api/v1/interpret

Query Parameters

Parameter Type Description
q string Natural language query to parse (e.g. "AI fintech in SF, not Stripe").

Example

curl -X POST "https://trycanonical.ai/api/v1/interpret?q=AI+fintech+in+SF+not+Stripe" \
  -H "x-api-key: YOUR_API_KEY"

# Response
{
  "query": "AI fintech in SF not Stripe",
  "interpreted_criteria": {
    "description": "AI fintech companies",
    "location": { "city": "San Francisco" },
    "exclude_company_names": ["Stripe"]
  },
  "exclusion_resolution": [
    { "input": "Stripe", "resolved": true, "name": "Stripe", "domain": "stripe.com" }
  ]
}

Pass the resolved domain to /api/v1/search via ?exclude_company_domain=stripe.com to lock the exclusion in deterministically. If the query states a constraint the engine can't map to a filter (e.g. profitable, household name), it's returned in an unmapped array inside interpreted_criteria instead of being dropped silently — surfacing what was understood but not applied.

Lookup Companies by Name

Resolve free-text company names to canonical candidates. Each input name returns ranked candidates plus a disambiguation recommendation: when auto_resolve_recommended is true the match is unambiguous (use primary_candidate_domain); when false the name is a genuine toss-up — surface the candidates for the user to choose. Costs 1 credit when at least one candidate is returned (a no-match lookup is free), rate-limited. Response includes credits_used and credits_remaining.

Confidence & auto-resolve policy. confidence is high (one candidate, or all candidates are alias domains of a single entity), medium (multiple distinct entities but the top one clearly dominates — at least 5× the next entity's headcount and ≥100 employees, or the only candidate with a description), or low (comparable distinct entities). Under the default auto_when_confident mode, high and medium auto-resolve; low asks. Invariant: primary_candidate_domain is non-null and exactly one candidate has is_primary: true only when auto_resolve_recommended is true; when asking, primary_candidate_domain is null and every candidate has is_primary: false (the list is still ranked best-guess-first, but no candidate is authoritative).

POST /api/v1/lookup

Query Parameters

Parameter Type Default Description
name string (repeatable) required One or more free-text company names. Repeat to look up several at once. Max 20 per call.
k integer 5 Candidates to return per name (1–25).
disambiguation_mode string auto_when_confident Override the recommendation: auto_when_confident (default), always_auto, or always_ask.

Example

curl -X POST "https://trycanonical.ai/api/v1/lookup?name=Stripe&name=Square&k=3" \
  -H "x-api-key: YOUR_API_KEY"

# Response
{
  "results": {
    "Stripe": {
      "confidence": "medium",
      "auto_resolve_recommended": true,
      "primary_candidate_domain": "stripe.com",
      "candidates": [
        { "name": "Stripe", "domain": "stripe.com", "headquarters": "San Francisco, CA, US", "description": "Online payment processing...", "is_primary": true, "payload_richness": "full" }
      ]
    },
    "Square": {
      "confidence": "low",
      "auto_resolve_recommended": false,
      "primary_candidate_domain": null,
      "candidates": [
        { "name": "Block", "domain": "block.xyz", "is_primary": false, "payload_richness": "full" },
        { "name": "Square Yards", "domain": "squareyards.com", "is_primary": false, "payload_richness": "full" }
      ]
    }
  },
  "credits_used": 1,
  "credits_remaining": 999
}

Errors

The API returns standard HTTP status codes. Error responses include a JSON body with a detail field.

Status Meaning Body
401 Missing or invalid API key {"detail": "API key required. Include x-api-key header."}
406 SSE requested with structured body Structured mode is batch JSON only — remove Accept: text/event-stream or switch to NL mode.
422 Both or neither search mode supplied Pass exactly one of ?q= or a structured JSON body, not both and not neither.
402 Insufficient credits {"detail": "Insufficient credits. Have X, need Y. Purchase more at /billing."}
429 Rate limit exceeded {"error": "Rate limit exceeded"}
500 Internal server error {"detail": "Internal server error"}

Credits

Search / similar: each strong-match result row consumes 1 credit — a search returning 20 results uses 20 credits. Lookup (/api/v1/lookup) costs 1 credit per call when it returns at least one candidate (a no-match lookup is free), regardless of how many names or candidates. /api/v1/interpret is free. Credits used and remaining are included in every billable response.