Multi-Tenant Integration
Model each of your customers as a profile, connect their accounts, scope API keys per tenant, and route webhooks back to the right customer.
If you're building social features into your own product, your customers each bring their own social accounts. Zernio's tenant boundary for this is the profile: one profile per customer, their connected accounts inside it, and your database holding the mapping.
The hierarchy:
Your API key (team)
└── Profiles ← one per customer of yours
└── Connected accounts ← their Instagram, TikTok, X, ...
└── Posts, inbox, analyticsEvery new team starts with a profile named "Default". You can use it for your own accounts and create one profile per customer alongside it.
Step 1: Create a profile per customer
When a customer signs up in your app, create their profile and store the returned profile._id on your customer record. Profile names must be unique within your team, so your internal customer ID makes a good name.
const { data } = await zernio.profiles.createProfile({
body: {
name: 'customer_8f3a2', // unique per team
description: 'Acme Corp',
},
});
// Store data.profile._id on your customer recordresult = client.profiles.create_profile(
name="customer_8f3a2", # unique per team
description="Acme Corp",
)
# Store result["profile"]["_id"] on your customer recordcurl -X POST "https://zernio.com/api/v1/profiles" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "customer_8f3a2", "description": "Acme Corp" }'See Create profile for all fields.
Step 2: Let them connect their accounts
Start the OAuth flow with the customer's profileId so the account lands in their profile. For a fully white-label experience (your UI, no Zernio-hosted selection screens), use headless mode.
curl "https://zernio.com/api/v1/connect/instagram?profileId=PROFILE_ID&redirect_url=https://your-app.com/callback" \
-H "Authorization: Bearer YOUR_API_KEY"
# → { "authUrl": "..." } — redirect your customer's browser thereThe connecting accounts guide covers the full flow, including headless mode and platforms that need a secondary selection (Facebook Pages, LinkedIn orgs, Pinterest boards).
Subscribe to the account.connected webhook to learn about new connections server-side. Its payload carries both accountId and profileId, which is exactly the mapping you want to persist:
{
"event": "account.connected",
"account": {
"accountId": "665f1c2e8b3a4d0012345678",
"profileId": "664a0b1c2d3e4f0011223344",
"platform": "instagram",
"username": "acmecorp"
}
}account.disconnected fires with the same identifiers when a token dies or a customer removes access, so you can prompt them to reconnect.
Step 3: Post on their behalf
List a customer's accounts by filtering on their profile, then create posts with those account IDs:
const { data } = await zernio.accounts.listAccounts({
query: { profileId: customer.zernioProfileId },
});
await zernio.posts.createPost({
body: {
content: 'Posted from your app!',
platforms: data.accounts.map((a) => ({
platform: a.platform,
accountId: a._id,
})),
publishNow: true,
},
});data = client.accounts.list_accounts(profile_id=customer.zernio_profile_id)
client.posts.create(
content="Posted from your app!",
platforms=[
{"platform": a["platform"], "accountId": a["_id"]} for a in data["accounts"]
],
publish_now=True,
)curl "https://zernio.com/api/v1/accounts?profileId=PROFILE_ID" \
-H "Authorization: Bearer YOUR_API_KEY"Posts validate accountId against your whole team, not against a profile. The profile is an organizational boundary, so keep the account-to-customer mapping in your database (Step 2 gives it to you) and only pass a customer their own account IDs.
Step 4: Route webhooks back to the right customer
Webhook endpoints are configured once per team (up to 10), not per profile. Run a single endpoint and route internally:
- Post events (
post.published,post.failed, ...) carry anaccountIdon each platform entry. Look it up in the mapping you stored in Step 2 to find the customer. - Account events (
account.connected,account.disconnected) carryprofileIddirectly.
See the webhooks overview for delivery, retries, and signature verification.
Scoped API keys
By default an API key can touch every profile in your team. For stricter isolation, create keys scoped to specific profiles with the scope and profileIds fields, and optionally make them read-only:
curl -X POST "https://zernio.com/api/v1/api-keys" \
-H "Authorization: Bearer YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "acme-corp-readonly",
"scope": "profiles",
"profileIds": ["664a0b1c2d3e4f0011223344"],
"permission": "read"
}'Use cases:
- A per-tenant backend service that can only see its own customer's profile.
- Read-only keys for analytics dashboards.
- Keys with
expiresIn(days) for temporary access.
See Create API key for details.
Offboarding a customer
When a customer churns:
- Disconnect their accounts. Active connected accounts block profile deletion.
- Delete the profile. Any remaining disconnected accounts and provisioned WhatsApp numbers are moved to another of your profiles, never deleted, so nothing is lost by accident.
Related
- Connecting accounts - the full OAuth and headless flows
- Idempotency & safe retries - make your posting pipeline retry-safe
- Webhooks - delivery, signatures, and all event payloads