What's new in the Knock2 API
A wave of new features for teams building on Knock2: per-tenant credit controls, fleet-wide reads and webhooks, full tenant lifecycle management, and a smarter enrichment engine.
Explore the API DocsEverything that shipped
If you manage customer workspaces ("tenants") under a parent Knock2 account, this release is for you. Here's the quick tour, with details and examples below.
Per-tenant credit limits
Allocate a slice of your credit pool to each tenant, in credits, contacts, or accounts, on the schedule you choose. Entirely via API.
Usage reporting
Real per-tenant usage numbers, plus a fleet-wide view of every tenant's allocations and consumption in one call.
Limit-reached webhooks
Get notified the moment a tenant exhausts its allocation, instead of finding out from a support ticket.
Cross-tenant reads, one key
Query any of your tenants' accounts, contacts, activity, and scores with your parent API key. No more juggling one key per tenant.
Parent-level webhook fan-in
One webhook subscription on your parent account now covers every tenant, including ones you create later.
Tenant lifecycle management
Rename a tenant, change its domain with full identification re-provisioning, deactivate, and reactivate. All via API.
Full-featured provisioning
New tenants come up with the complete platform feature set enabled and identification live before the create call returns.
Upgraded enrichment engine
A unified multi-source waterfall with smarter identity matching and caching. Better fill rates, same flat 1-credit price.
Spin up fully-loaded workspaces in one call
More than before, and faster:
- Identification is provisioned synchronously. IP-to-company and person identification are enabled for the new tenant before the call returns. The tracking script at
script_urlis live immediately. No polling, no waiting on a background job. - The full platform feature set is enabled by default, including Plays, webhooks, and CRM integrations. A freshly provisioned tenant behaves like a fully onboarded workspace from minute one.
- You get a ready-to-use API key for the tenant in the response. It's returned exactly once in plaintext, so store it immediately.
- You can set credit limits in the same call, so "create a tenant with a 100-credit allowance" is one request, not two.
// POST /v1/tenants
{
"name": "Acme Corp",
"domain": "acme.com",
"limits": [
{ "type": "credits", "value": 100, "period": "lifetime" }
]
}Yes, this is new. PATCH /v1/tenants/{product_slug} accepts:
- name: a simple display-value change.
- domain: treated as a full re-provisioning operation, not just a column write. Our identification providers are re-registered against the new domain, so IP-to-company and person matching immediately track the new site instead of silently continuing to resolve against the old one. The new domain is also validated and checked for conflicts before anything is written.
product_slug is never updatable. It's the tenant's permanent identifier.
Yes: POST /v1/tenants/{product_slug}/activate is the mirror of DELETE /v1/tenants/{product_slug}. It reactivates the workspace and restores identification service. Reactivation is the supported path for a returning domain, since re-creating the same domain from scratch will return a 409.
Per-tenant credit limits
You can now allocate a slice of your own credit pool to each tenant, programmatically, in natural units, on a window you choose. No more single undifferentiated pool where one noisy tenant can eat your entire plan. This is the feature that makes reseller and white-label economics safe to build on: give every customer a free taste, meter your paid tiers, and never wake up to a drained pool.
Each tenant can carry up to three limit types simultaneously. All are enforced, and the most restrictive wins:
| Type | What it caps | Blocks |
|---|---|---|
credits | Total credit spend | Everything (identification, enrichment, prospecting) |
contacts | Unique contacts identified | The contact pipeline only |
accounts | Unique accounts identified | The account pipeline only |
Each limit runs on a period: billing_period (your plan's cycle), month, week, day, or lifetime.
// PUT /v1/tenants/{product_slug}/limits
{
"limits": [
{ "type": "credits", "value": 100, "period": "lifetime" },
{ "type": "contacts", "value": 10, "period": "week" }
]
}PUT replaces the entire set, so it's idempotent and there's never partial-state ambiguity. DELETE /v1/tenants/{slug}/limits removes all limits, making the tenant unlimited within your pool. And GET /v1/tenants/{slug}/limits shows each limit with live used and remaining values for the current window.
Limit values are always denominated in the thing they cap: 10 contacts means 10 contacts, not 10 credits' worth of contacts. Every response also echoes equivalent_credits so you can reason about cost:
{ "type": "contacts", "value": 10, "period": "week",
"equivalent_credits": 60, "used": 4, "remaining": 6 }Yes, deliberately. Most tenants never use their full slice, so overbooking is normal. Allocate 10 × 500 credits against a 1,500-credit plan if that fits your business. GET /v1/tenants returns allocated_total_credits alongside your plan_credits so you can watch the overbooking ratio. Your own plan-level cap still protects you regardless: it hard-stops the whole account if reached.
Identification for that tenant stops cleanly. It's a hard stop, scoped to whichever pipeline the exhausted limit covers. Your other tenants and your own workspace are unaffected. The window then resets on schedule (a week limit resets Monday 00:00 UTC, and so on), or you can raise the limit at any time.
Within about 3 minutes. Enforcement status is cached briefly for performance, so treat limit changes as near-real-time rather than instantaneous. Note this cuts both ways: lowering a limit below a tenant's current usage blocks them on the next check.
No. The API key provisioned for each tenant carries tenants:read, so a tenant can check its own remaining balance (useful if you surface it in your product), but only a key belonging to the parent account can write limits. Limits are your lever, always.
Subscribe to the new tenant.limit_reached webhook event. It fires to your parent-level webhook subscriptions the moment an allocation is exhausted, with the tenant's slug, which limit tripped, and the window:
{
"product_slug": "acme_com_yourco_com",
"limit_type": "credits",
"limit_value": 100.0,
"period": "lifetime",
"window_start": "2026-08-01 00:00:00"
}Read any tenant's data with one key
No. This is one of the biggest quality-of-life changes. Your parent API key can now read any direct child tenant's data on all the read endpoints:
GET /v1/accountsandGET /v1/accounts/{id}GET /v1/contactsandGET /v1/contacts/{id}GET /v1/activityGET /v1/scoresandGET /v1/scores/recent
Pass the target tenant either way:
GET /v1/contacts
X-Knock-Tenant: acme_com_yourco_comor GET /v1/contacts?product_slug=... (the header wins if both are present). Omit both and the endpoint reads your own workspace's data, exactly as before. Fully backward compatible.
A few properties worth knowing:
- Authorization is strict: only direct children of your account resolve. Anything else returns a 404. The API never confirms whether a slug you don't own exists.
- Rate limiting and request accounting stay attributed to your key, regardless of which tenant you're reading. One key, one predictable rate budget.
- If you manage 200 tenants, you now hold one secret for your entire read path instead of 200.
One subscription, all tenants
No. A webhook subscription on your parent account automatically receives events from every child tenant, including tenants created after you registered the subscription. There's no per-tenant registration step, and no failure mode of forgetting one and having that customer silently receive nothing.
Every payload identifies its tenant, so routing on your side is one field:
- Payloads include the originating
product_slug. - Deliveries are HMAC-SHA256 signed (
X-Knock-Signature); retrieve your full signing key any time viaGET /v1/webhooks/{id}/secret. - If a tenant also has its own subscription for the same event, both fire. Deliveries carry IDs, so dedupe on the receiving end if you run both.
| Event | When it fires |
|---|---|
account.identified | A company was identified from a site visit |
contact.identified | An individual person was identified on the site |
score.changed | A lead score was updated for an account or contact |
play.triggered | A play (automation) fired for a contact or account |
signal.detected | A buyer signal was detected for a watched account or contact |
tenant.limit_reached | A child tenant's credit, contact, or account allocation was exhausted |
product_slug.Know exactly what every tenant consumed
GET /v1/tenants/{product_slug}/usage returns that tenant's own usage (never its parent's or siblings') for any window:
GET /v1/tenants/acme_com_yourco_com/usage?period=week
period accepts billing_period (default), month, week, day, or lifetime. This is the same number the limit system enforces against, so what you see is exactly what's counted.
For the fleet view, GET /v1/tenants lists every tenant with its full allocation set and per-limit used/remaining, plus the account-level allocated_total_credits vs plan_credits. That's a complete billing dashboard for your book of business in one paginated call. Build customer-facing usage meters, internal margin reports, or overage alerts on top of it without any data warehousing.
tenants:read(new): list and get tenants, read limits and usage.tenants:write: create, update, deactivate/reactivate tenants, and write limits.
Per-tenant keys are provisioned with tenants:read (self only) plus read/write on their own data. They never receive tenants:write.
A smarter engine behind POST /v1/enrich
The endpoint's contract is the same: send an email (optionally a LinkedIn URL), get back a merged enriched profile. But the engine behind it was rebuilt:
- One unified waterfall across multiple premium data sources, tried in sequence until the profile is filled. You get the union of what the sources know, not one vendor's best guess.
- Smarter identity matching. The waterfall now keys on the person's identity, not just the literal email string. A personal email no longer blocks a work-email lookup, and identity can be recovered by name plus company when direct keys fail.
- Intelligent caching. We stop re-asking sources questions we've recently asked, and cached data follows a newest-wins policy bounded by enrichment timestamps. Fresh data, without paying latency for redundant lookups.
- Non-destructive merging. Enrichment only fills genuinely empty fields on the contact. It never overwrites data you already have.
The response's contact_id plugs straight into GET /v1/contacts/{id}, and inline lead scoring runs automatically on enriched contacts, so a score is available right away.
Behavior notes
- Deactivation is a true master switch. A deactivated tenant's API access, identification, and script all stop together. No half-alive states. Reactivation restores all of it together too.
- 404, not 403. Anywhere you reference a tenant you don't own, the API returns 404. It never confirms the existence of slugs outside your account.
- Webhook URLs must be HTTPS. Enforced at registration.
- Limit changes propagate within ~3 minutes (enforcement caching). Design flows accordingly.
- Store per-tenant API keys at creation time. They're returned in plaintext exactly once.
Ready to build?
The full API reference, with every endpoint, schema, and example in this post, lives in our interactive docs.
View the API Reference




