Kestrel

Quickstart

Generate an API key from /app/api-keys. Test with the lookup endpoint:

curl https://api.kestrel.in/v1/company/lookup \
  -H "Authorization: Bearer kst_test_..." \
  -G --data-urlencode "cin=U24230TG2016PTC108429"

Responses are JSON. Every populated field includes a key in _provenance describing where the value came from and when we last verified it.

Auth

Bearer-token in the Authorization header. Keys are issued with a prefix that identifies environment: kst_live_ for production, kst_test_ for sandbox. Keys can be scoped per-endpoint.

Rate limits

Sliding-window 60-second buckets, Redis-backed, per key. 429 responses include Retry-After. Limits scale with plan:

Free10 req/min
Starter20 req/min
Growth60 req/min
Scale600 req/min
EnterpriseNegotiated

Errors

4xx errors return { "error": { "code": "...", "message": "..." } }. Common codes:

auth_invalidMissing or malformed API key
auth_revokedKey has been revoked
rate_limitedPer-key rate limit hit, see Retry-After
insufficient_creditsPlan credits exhausted
identifier_ambiguousMultiple companies match, supply more identifiers
identifier_not_foundIdentifier not present in graph
validation_failedBody or query params failed schema

Provenance

Every response includes _provenance, a per-field map. Each entry has source, source_url, legal_basis, captured_at, last_verified_at.

The legal_basis field is one of:

  • dpdp_3c_ii_legal_mandate, statutory public via DPDP Section 3(c)(ii).
  • dpdp_3c_ii_self_published, made public by the data principal.
  • consent, processed with explicit consent (Phase 2).
  • licensed, licensed from a third party with audit trail.

Endpoints

GET/v1/company/lookup

Look up one company by any identifier. Returns null if not found.

cinCorporate Identification Number
llpinLLP identification number
gstinAny one GSTIN; resolved to parent company
iec10-digit IEC
panPAN of the entity
nameLegal name, normalized server-side
websiteDomain (no protocol)
Example response
{
  "company_id": "c_aravind_pharma",
  "cin": "U24230TG2016PTC108429",
  "name": "Aravind Pharma Private Limited",
  "status": "active",
  "entity_class": "private",
  "incorporation_date": "2016-05-30",
  "state": "Telangana",
  "city": "Hyderabad",
  "pincode": "500032",
  "industry_nic_code": "24230",
  "authorized_capital": 50000000,
  "paid_up_capital": 47500000,
  "employee_band": "201_500",
  "turnover_band": "25_100_cr",
  "website": "aravindpharma.in",
  "identifiers": [
    { "type": "GSTIN", "value": "36AAFCA8261N1ZJ", "verified": true },
    { "type": "IEC",   "value": "0916041128",      "verified": true }
  ],
  "directors": [
    { "din": "07182934", "name": "Aravind Kumar Reddy", "designation": "Managing Director", "appointment_date": "2016-05-30", "current": true }
  ],
  "trade": {
    "exporter": true,
    "importer": true,
    "first_export_date": "2018-09-12",
    "hs_chapters": ["30", "29"],
    "top_destinations": ["Nigeria", "Kenya", "Philippines", "Sri Lanka"]
  },
  "_provenance": {
    "cin": {
      "source": "MCA21",
      "source_url": "https://mca.gov.in",
      "legal_basis": "dpdp_3c_ii_legal_mandate",
      "captured_at": "2026-05-28T08:14:00Z",
      "last_verified_at": "2026-05-28T08:14:00Z"
    },
    "iec": {
      "source": "DGFT",
      "legal_basis": "dpdp_3c_ii_legal_mandate",
      "last_verified_at": "2026-05-29T14:18:00Z"
    }
  },
  "credits_charged": 1,
  "credits_remaining": 9421
}
POST/v1/company/enrich

Bulk enrich up to 5,000 identifiers. Returns one record per resolved input.

Example response
{
  "results": [
    { "input": { "cin": "U24230TG2016PTC108429" }, "resolved": true, "company": { ... } },
    { "input": { "gstin": "27AABCA9821H1Z5" },      "resolved": true, "company": { ... } },
    { "input": { "name": "ACME PVT LTD" },           "resolved": false, "candidates": [ ... ] }
  ],
  "credits_charged": 12,
  "credits_remaining": 9409
}
GET/v1/company/search

Paginated search with filters.

industryNIC code, comma-separated
stateState name or RoC code
cityCity name
entity_classprivate | public | llp | opc | section8 | foreign
exportertrue | false
hs_chapterTwo-digit HS chapter, comma-separated
incorporation_afterYYYY-MM-DD
employee_band1_10 | 11_50 | 51_200 | 201_500 | 500_plus
turnover_band1_5_cr | 5_10_cr | 10_25_cr | 25_100_cr | 100_250_cr | 250_plus_cr
pagePage number, default 1
page_sizeDefault 50, max 200
Example response
{
  "results": [ ... 50 company records ... ],
  "page": 1,
  "page_size": 50,
  "total": 1218,
  "credits_charged": 25,
  "credits_remaining": 9384
}
GET/v1/signals

Signal feed, filterable.

sinceISO-8601 timestamp
typeSignal type, comma-separated
industryNIC code, comma-separated
stateState name
min_strengthInteger 1 to 10
Example response
{
  "signals": [
    {
      "id": "sg_2026_05_29_94821",
      "company_id": "c_aravind_pharma",
      "type": "funding_round",
      "title": "Series A, Rs 38 Cr raised",
      "detail_jsonb": { "amount_inr": 380000000, "lead": "Stellaris Venture Partners", "form": "PAS-3" },
      "strength": 10,
      "signal_date": "2026-05-08",
      "detected_at": "2026-05-29T09:14:00Z",
      "source": "MCA21"
    }
  ],
  "credits_charged": 4,
  "credits_remaining": 9380
}
GET/v1/trade/profile

Export-import profile of one company.

Example response
{
  "company_id": "c_madhuban_foods",
  "exporter": true,
  "importer": false,
  "first_export_date": "2011-08-22",
  "primary_hs_chapters": ["04", "19", "21"],
  "top_destination_countries": ["United States", "Canada", "Australia", "UAE"],
  "estimated_annual_export_band": "100_250_cr",
  "ports_used": ["INMUN", "INJNP", "INMAA"],
  "last_activity_date": "2026-05-18",
  "events": [ ... last 12 months of trade events ... ]
}

Webhooks

POST to your configured URL on new signals matching a saved subscription. Body is the signal record, headers carry HMAC-SHA256 signature in X-Kestrel-Signature. Compute over the raw body using your webhook secret.

import crypto from 'crypto';
const sig = req.headers['x-kestrel-signature'];
const expected = crypto
  .createHmac('sha256', process.env.KESTREL_WEBHOOK_SECRET)
  .update(req.rawBody)
  .digest('hex');
if (sig !== expected) return res.status(401).end();

Postman collection

Importable collection mirroring this doc, with environment variables for key and base URL: kestrel.postman_collection.json. Download from your dashboard.

Changelog

  • 2026-05-29 v1.4: Added /v1/trade/events. Provenance on every response.
  • 2026-05-12 v1.3: Bulk enrich up to 5,000 per call.
  • 2026-04-22 v1.2: HS-chapter filter on /v1/company/search.
  • 2026-04-01 v1.1: Webhook signatures (HMAC-SHA256).
  • 2026-03-15 v1.0: Public launch.