Skip to main content
A partner serves many customers of its own through NDI. Each customer is a tenant: a separate NDI organization with its own workspaces, files, jobs, domains, access labels, schemas and limits. The partner holds one API key for all of them and names the tenant on every request.

Acting for a tenant

Send these headers with the partner key on every /v1 route: Everything the request reads or creates belongs to that tenant alone: another tenant’s resources answer 404 exactly as if they did not exist. Platform domains and extraction schemas stay visible to every tenant. A malformed X-NDI-Tenant answers 404 like an unknown one, so no answer tells you which tenants exist. A partner key acts for a tenant only through X-NDI-Tenant: X-NDI-External-Account is for service keys that are not a partner’s. The legacy /api/v1 routes, including /api/v1/example and /api/v1/health/load, answer 403 to a partner key.

Managing tenants

Call these with the partner key and without X-NDI-Tenant. PUT accepts display_name (up to 128 characters), disabled, data_retention, retention_tier and limits (rpm, max_concurrent_jobs). Creating a tenant past the partner’s tenant cap answers 409 tenant_limit_reached; disabled tenants count toward the cap.
  • rpm is the tenant’s requests per minute. A request over it answers 429.
  • max_concurrent_jobs is how many parse, split, classify, categorize, extract, ground and agent jobs the tenant may have running at once. More jobs wait in the queue instead of being refused. NDI’s own per-tenant cap applies when it is lower, so this setting can lower a tenant’s concurrency but not raise it. Ingestion isn’t counted.
  • A limit above the partner’s own is lowered to it. The partner’s own limit is the lower of its own value and its partner-wide cap (partner.rpm, partner.concurrent_jobs), when one is set.
  • Lowering the partner’s limits later doesn’t rewrite existing tenants. The partner-wide caps bound all of its tenants together, and so each of them too.
  • data_retention is how the tenant’s document operations retain data, short or zero_retention. Omit it on create to inherit your own setting, or on update to leave the tenant’s setting unchanged. The tenant is returned with the setting in force for it: on a deployment that has not enabled zero retention, that is short. Under zero_retention, results are never stored: a request must pass wait_seconds and read the result from the response, or it answers 422 wait_required_for_zero_retention. Setting zero_retention on a deployment that has not enabled it answers 422 invalid_request.
  • retention_tier is the tenant’s account tier for job retention, paid or free. A paid tenant’s job inputs, outputs and processing files are removed 48 hours after the job completes, a free tenant’s 7 days after; a failed or cancelled job counts from the failure or cancel. A tenant never given a tier is paid. Omit it on update to leave the tier unchanged. A change applies to jobs that finish afterwards. See Data retention.

Purging a tenant

DELETE /v1/tenants/{external_ref}?purge=true permanently deletes everything the tenant holds. It cannot be undone. Without purge=true, DELETE only disables the tenant and keeps its data. Call it with the partner key and no X-NDI-Tenant, like the other management routes. The call answers 202 with a tenant_purge job. The tenant is disabled at once and its running jobs are cancelled. Every file’s bytes, every result, cache entry and webhook payload of the tenant is then deleted, and its keys are revoked. Only anonymized metadata is kept: the job records (ids, kinds, statuses, timestamps, pages, units, bytes, credits, token counts per model and error codes, with no job names, inputs, results or error messages), an anonymized record for each deleted file and for each console user whose only organization it was, the tenant’s workspaces and keys as renamed rows with no settings and no usable key hash, and its other records (memberships, invitations, uploads, webhook endpoints and events, access labels) marked deleted, with no names, addresses, URLs, secrets or payloads. See What we keep after deletion. Poll the job with GET /v1/jobs/{job_id} using the partner key and without X-NDI-Tenant; a partner may read only the purge jobs it owns this way. When the job has succeeded, its result reports what the verification pass found: A purge that ends with verified: false is still a succeeded job: the report says what is left, and repeating the DELETE runs the purge again. A purge already queued or running is returned as it is. Once a purge has verified clean, the external_ref no longer resolves: repeating the call answers 404 tenant_not_found, and a later PUT creates a new, empty tenant.

Usage across tenants

With the partner key and no X-NDI-Tenant, GET /v1/usage?group_by=tenant returns one bucket per tenant, keyed by its external_ref, over the same period_start/period_end window as any usage report. Disabled tenants are included, since their usage is still owed. Only your own tenants are counted. One tenant’s usage is the ordinary GET /v1/usage with X-NDI-Tenant. group_by=tenant with X-NDI-Tenant, a workspace_id, or a key that is not a partner’s answers 422 invalid_request. Any other grouping from the partner key without X-NDI-Tenant, and any request with X-NDI-External-Account, answers 400 tenant_required.