Get started
Reseller quickstart
Build one server-side integration that safely provisions and operates OnCore resources for many customer tenants.
OnCore's reseller hierarchy is organization -> tenant -> assistant. Give each end customer a tenant, keep one organization API key in your backend, and select the customer on every request with X-Tenant-Id.
This is the supported multi-tenant pattern. Never expose an organization key to a browser, mobile app, customer, or generated client.
What you need#
- An organization API key from the API Keys page in the OnCore dashboard.
- The OnCore tenant ID for each customer you operate.
- A durable mapping from your customer ID to that tenant ID.
The public Core API does not create or list tenants. Tenant creation is part of your OnCore account setup. Once a tenant ID exists, the same organization key can use all tenant-scoped public routes on its behalf.
Create a tenant-scoped client#
Put tenant selection in one backend helper so a route cannot accidentally send a request in the wrong customer's context:
const ONCORE_BASE_URL = "https://core-api.heysadie.ai";
export function oncoreForTenant(tenantId: string) {
if (!tenantId) throw new Error("An OnCore tenant ID is required");
return async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
const apiKey = process.env.ONCORE_ORG_API_KEY;
if (!apiKey) throw new Error("ONCORE_ORG_API_KEY is not configured");
const headers = new Headers(init.headers);
headers.set("Authorization", `ApiKey ${apiKey}`);
headers.set("X-Tenant-Id", tenantId);
headers.set("Content-Type", "application/json");
const response = await fetch(`${ONCORE_BASE_URL}${path}`, {
...init,
headers,
});
const payload = await response.json();
if (!response.ok || !payload.success) {
throw new Error(payload.error?.code ?? `ONCORE_HTTP_${response.status}`);
}
return payload.data as T;
};
}Resolve tenantId from the authenticated customer on your server. Do not accept an arbitrary tenant ID from a request body or query string without checking that it belongs to that customer.
const customer = await db.customers.findByUserId(session.user.id);
const oncore = oncoreForTenant(customer.oncoreTenantId);
const assistants = await oncore<{ assistants: unknown[] }>("/assistants");Provision each customer#
Use the same public workflow inside every tenant:
- Select voice, model, and transcriber IDs from the lookup endpoints.
- Create the customer's assistant.
- Search and purchase a phone number, then assign it to the assistant.
- Set the assistant's
serverUrlto a customer-aware webhook endpoint in your backend. - Store the returned OnCore resource IDs beside your own customer record.
The Quickstart contains the exact request bodies for steps 1 through 3. Add X-Tenant-Id to each request shown there when using an organization key.
For browser calling, web chat, SMS, and WhatsApp, keep the same tenant selection. WhatsApp onboarding accepts the organization key and X-Tenant-Id pattern too.
Keep data isolated#
- Derive the tenant ID from your authenticated customer record on every request.
- Keep OnCore IDs namespaced by customer in your database; an assistant ID alone is not sufficient authorization.
- Verify the customer mapping before proxying reads, writes, or WebRTC session creation.
- Use one webhook URL that resolves the tenant or resource from the event, then applies your own authorization and routing.
- Log the customer ID, tenant ID, method, path, OnCore request outcome, and your correlation ID. Never log the API key or webhook secret.
A resource from another tenant returns 404, even if it exists. This prevents cross-tenant enumeration; treat that response as either a stale ID or a tenant-mapping error.
Understand the boundary#
An organization key does not provide cross-tenant list endpoints. Fan-out reporting must iterate tenant IDs and make one tenant-scoped request per customer. The two organization-level audit routes are the only documented exceptions that do not take X-Tenant-Id.
There is no public API for tenant lifecycle, reseller billing, white-label configuration, or importing SIP credentials. The complete public boundary is maintained in Capabilities and availability.
Next steps#
- Organization-level access - exact headers, failures, and organization-only audit routes.
- Authentication - tenant keys, organization keys, and key rotation.
- Webhook verification - authenticate events before routing them into customer systems.