Skip to content

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:

oncore.ts
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.

Customer request
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:

  1. Select voice, model, and transcriber IDs from the lookup endpoints.
  2. Create the customer's assistant.
  3. Search and purchase a phone number, then assign it to the assistant.
  4. Set the assistant's serverUrl to a customer-aware webhook endpoint in your backend.
  5. 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#