# Use your own platform apps

Connect your customers' accounts through your own app on each platform, so the consent screen shows your brand and the rate limits are your app's.

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';

By the end of this page, your customers connect through your own app on a platform: the consent screen shows your app's name and logo, and the platform's quotas are your app's, not Zernio's.

## Before you start

You need an API key with full scope, from the account owner or a full-access team member. Profile-scoped keys cannot manage apps.

You also need an app on the platform, created in its developer console. Accounts connected without an app id keep running on Zernio's app, exactly as before.

These platforms accept your own app today:

| Platform | Where the redirect URI goes | Approval before outside accounts connect |
|---|---|---|
| Instagram | Instagram product, API setup with Instagram login, OAuth redirect URIs | App Review, advanced access |
| Facebook | Facebook Login for Business, Valid OAuth redirect URIs | App Review, advanced access |
| Threads | Access the Threads API use case, Redirect callback URLs | App Review |
| WhatsApp | Not used: numbers connect with credentials | System user token with WhatsApp messaging and management |
| YouTube | Google Cloud OAuth client (Web application), Authorized redirect URIs | Google verification of the consent screen |
| Google Business Profile | Google Cloud OAuth client (Web application), Authorized redirect URIs | Google approval for the Business Profile APIs |
| Gmail, Calendar and Drive (`google`) | Google Cloud OAuth client (Web application), Authorized redirect URIs | Google verification for Gmail's restricted scopes |
| Outlook, Calendar and OneDrive (`microsoft`) | Azure app registration, Authentication, Web redirect URI | None |
| X | User authentication settings, OAuth 2.0, Callback URI | None |
| LinkedIn | Auth tab, Authorized redirect URLs | Community Management API, for comments and analytics |
| TikTok | Login Kit, Redirect URI | Content Posting API with Direct Post |
| Pinterest | App settings, Redirect URIs | Standard access |
| Snapchat | Snap Business Manager, OAuth Apps, Redirect URI | Public Profile API |
| Reddit | Your web app, redirect uri | Reddit API access approval |
| Slack | OAuth & Permissions, Redirect URLs | None for your own workspace |

`GET /v1/connect/oauth-apps` lists the same platforms in `platforms`, with each one's redirect URI and scopes.

## Step 1: Add the app

Send the app's client ID and secret with `POST /v1/connect/oauth-apps`. Zernio checks the keys with the platform before saving them.

<Tabs items={['Node.js', 'curl']}>
<Tab value="Node.js">
```typescript
import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data } = await zernio.connect.createOAuthApp({
  body: {
    platform: 'youtube',
    label: 'Acme',
    clientId: '1234567890-abc.apps.googleusercontent.com',
    clientSecret: 'GOCSPX-xxxxxxxx',
  },
});

const appId = data.app.id; // pass as oauthAppId when connecting
```
</Tab>
<Tab value="curl">
```bash
curl -X POST "https://zernio.com/api/v1/connect/oauth-apps" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "youtube",
    "label": "Acme",
    "clientId": "1234567890-abc.apps.googleusercontent.com",
    "clientSecret": "GOCSPX-xxxxxxxx"
  }'
```
</Tab>
</Tabs>

Response (`201`):

```json
{
  "app": {
    "id": "6720b1c4e8a9f0d2c3b4a5e6",
    "platform": "youtube",
    "label": "Acme",
    "clientId": "1234567890-abc.apps.googleusercontent.com",
    "scopeAreas": ["posting", "analytics", "comments"],
    "status": "ok",
    "accountCount": 0,
    "redirectUri": "https://zernio.com/api/v1/connect/youtube/callback",
    "webhook": null,
    "sharedWebhookUrl": null
  }
}
```

`status` is `ok` when the platform accepted the keys, and `unchecked` when it could not say. Slack, for example, only confirms an app on its first install.

Pass `scopeAreas` to ask for less than every area, such as `["posting"]` when your app is only approved for publishing. LinkedIn apps default to posting only.

## Step 2: Allow the redirect URI

Copy `redirectUri` from the response into your app's settings, in the place the table above names. The platform refuses the connect until it matches exactly.

## Step 3: Connect an account through the app

Call `GET /v1/connect/{platform}` as usual and add `oauthAppId`. The customer sees your app on the consent screen, and the account stays on your app afterwards.

<Tabs items={['Node.js', 'curl']}>
<Tab value="Node.js">
```typescript
const { data: connect } = await zernio.connect.getConnectUrl({
  path: { platform: 'youtube' },
  query: { profileId, oauthAppId: appId, redirect_url: 'https://your-app.com/callback' },
});
// Send the customer's browser to connect.authUrl
```
</Tab>
<Tab value="curl">
```bash
curl "https://zernio.com/api/v1/connect/youtube?profileId=66a1f0c2a4b9d3e8f1a2b3c4&oauthAppId=6720b1c4e8a9f0d2c3b4a5e6&redirect_url=https://your-app.com/callback" \
  -H "Authorization: Bearer $ZERNIO_API_KEY"
```
</Tab>
</Tabs>

Token refreshes, publishing, analytics and the inbox for that account all run on your app's keys from then on.

## Step 4: Set up events

Most platforms need nothing more. Four do:

- **Facebook and WhatsApp**: Zernio registers the webhook on your app when you add it. `webhook.status` says whether it worked.
- **Instagram**: also send `metaAppId` and `metaAppSecret`, the Meta app the Instagram app lives in, so Zernio can register its webhook.
- **TikTok**: set `sharedWebhookUrl` as the webhook callback URL in your TikTok app. Publishing works without it.
- **Slack**: send `slackSigningSecret` with the app, then paste `slackEvents.requestUrl` into Event Subscriptions and subscribe the listed bot events.

## How it behaves

- You can add any number of apps, several per platform. Nothing picks one for you: a connect without `oauthAppId` runs on Zernio's app.
- The client secret is never returned. Rotate it with `PATCH /v1/connect/oauth-apps/{id}`, and every account on the app picks up the new one.
- `POST /v1/connect/oauth-apps/{id}/test` checks the keys again. An `invalid` status means connects and token refreshes through the app fail until you rotate the secret.
- Removing an app with connected accounts needs `confirm=true`, and disconnects those accounts. `accountCount` says how many there are.
- Instagram apps cover Instagram login only. Ads connects, and WhatsApp's Embedded Signup, stay on Zernio's app.
- Adding a WhatsApp app replaces its webhook callback URL with Zernio's, so use a Meta app that serves only Zernio.
- A new Google Cloud project starts with a small YouTube upload quota (about 6 uploads a day). Ask Google for more before you move customers to it.

## If it fails

| Error | Cause | Fix |
|---|---|---|
| `OAUTH_APP_KEYS_REJECTED` | The platform rejected the client ID or secret. | Copy both again from the platform's console. Nothing was saved. |
| `OAUTH_APP_ALREADY_EXISTS` | You already added this client ID for this platform. | Use the app in `details.appId`, or rotate its secret with `PATCH`. |
| `INSUFFICIENT_PERMISSIONS` | The key is profile-scoped, or the caller is not the owner or a full-access member. | Use a full-scope key from the account owner. |
| The platform shows a redirect URI error | The redirect URI in your app does not match `redirectUri`. | Paste `redirectUri` exactly, with no trailing slash. |
| The consent screen says the app is not approved | The platform has not reviewed your app yet. | Finish the approval in the table above, or add the customer as a tester. |
| `webhook.status` is `failed` | Zernio could not register the webhook on your Meta app. | Read `webhook.error`. For Instagram, send `metaAppId` and `metaAppSecret` with `PATCH`. |

## Related

- [Add an OAuth app](/connect/create-oauth-app)
- [List your OAuth apps](/connect/list-oauth-apps)
- [Check an OAuth app's keys](/connect/test-oauth-app)
- [Get a connect URL](/connect/get-connect-url)
- [Build a platform](/multi-tenant)