---
title: "Connect ads for a platform"
method: GET
path: "/v1/connect/{platform}/ads"
tags: ["Connect"]
---

# Connect ads for a platform

`GET /v1/connect/{platform}/ads`

Unified ads connection endpoint. Creates a dedicated ads SocialAccount for the specified platform.

**Meta business login (opt-in).** Set `loginMode=business` for `facebook` or
`instagram` to use Facebook Login for Business and a Business Integration System User
token. No posting account is created or required. This mode always returns an authUrl;
it returns 503 when the server has no META_ADS_CONFIG_ID. Complete the dialog in a
browser. The callback creates or reconnects only the metaads account, preserving its
ID, history and scopedAdAccountIds. Non-empty successful subscription results replace
subscribedAdAccountIds to remove stale grants; an empty result leaves routing unchanged. A reconnect must grant
every previously scoped ad account (or every previous grant for an unscoped connection).
Missing or unverifiable grants return 409 before changing the account.

Pass `pageId` to select a granted Page for creatives and lead forms. API integrations
otherwise reuse the previous Page or sole granted Page. Multiple Pages without a selection
return 400 with available Page IDs for API integrations; restart with pageId.
Dashboard session logins use the sole current grant automatically or open the existing
Facebook Page picker for several grants, including reconnects. Selection completes
the Meta Ads connection. With no Pages granted the callback returns
400 with instructions to connect again and grant a Page.
Success redirects with connected=metaads, profileId and accountId.
Business login reports metadata.tokenType=system-user in GET /v1/accounts. An absent
Meta expires_in leaves tokenExpiresAt absent; no personal-token re-exchange occurs.
Subsequent classic requests can change the ad-account scope using the business token;
force=true requires loginMode=business to reconnect that connection.

**Same-token platforms (facebook, instagram, linkedin, pinterest).** The ads SocialAccount
(metaads, linkedinads, pinterestads) reuses the OAuth token of the parent posting account,
but only when an active parent exists and, for facebook and instagram, its stored token
carries ads_management and ads_read (linkedin and pinterest need no extra scope). In that
case no extra OAuth happens and the response is alreadyConnected: true.

When no such parent exists, or the scopes are missing, the endpoint returns an authUrl and a
full OAuth round trip is required. When a parent exists but carries no token usable for ad
accounts, the call fails with 400 RECONNECT_REQUIRED. Independently of the branch, the call
can return 403 ADS_ADDON_REQUIRED without the ads add-on and 402 PAYMENT_REQUIRED when the
billing gate is closed.

Meta Ads prerequisite: connecting Meta Ads (via facebook or instagram) requires a Facebook
Page. Not because the ad account is read through a Page, but because both parent posting
accounts are: the facebook flow only offers Pages you manage, and the instagram flow with
loginMethod=facebook_login only offers Instagram accounts linked to one of those Pages.
Without a Page there is no parent account to inherit a token from. A user who manages no
Facebook Page cannot complete this connection, and the facebook flow ends with
error=no_facebook_pages.

**Separate-token platforms (tiktok, twitter).** Starts the platform-specific marketing API
OAuth flow and creates an ads SocialAccount (tiktokads, xads) with its own token. If the ads
account already exists, returns alreadyConnected: true.
  - tiktok: accountId is OPTIONAL. With accountId, the new tiktokads account links to that posting account (parentAccountId set), so Spark Ads + standalone ads using the posting TT_USER identity become available. Without accountId, ads-only mode kicks in: the new tiktokads account has parentAccountId=null and standalone ads use a synthetic CUSTOMIZED_USER ("Brand Identity"); Spark Ads are unavailable because TikTok requires a posting account for them. The Brand Identity is configured separately via PATCH /v1/connect/tiktok-ads (or inline on POST /v1/ads/create via the brandIdentity field).
  - twitter (X Ads): accountId is REQUIRED. There's no ads-only mode, because tweets need to be authored by a real X user.

**Standalone platforms (googleads).** Starts the Google Ads OAuth flow and creates a
standalone ads SocialAccount (googleads) with no parent. If the account already exists,
returns alreadyConnected: true.

Ads accounts appear as regular SocialAccount documents with ads platform values (e.g., metaads, tiktokads) in GET /v1/accounts.

## Path parameters

- `platform` 'facebook' | 'instagram' | 'linkedin' | 'tiktok' | 'twitter' | 'pinterest' | 'googleads', required

## Query parameters

- `loginMode` 'classic' | 'business'
- `permissionLevel` 'full' | 'advertise'
- `pageId` string
- `profileId` string, required
- `accountId` string
- `redirect_url` string, uri
- `headless` boolean
- `force` boolean
- `adAccountId` string
- `adAccountIds` string[]

## Response `200`

Either an OAuth URL to redirect to, or confirmation that ads are already connected

- union
  - object — Ads already connected (no OAuth needed)
    - `alreadyConnected` boolean
    - `accountId` string
    - `platform` string
    - `username` string
    - `displayName` string
    - `tokenType` 'system-user' — Present for an existing business-login connection.
    - `scopedAdAccountIds` string[] — Echo of the persisted ad-account scope when the caller passed `adAccountId` / `adAccountIds`. Omitted when no scope is set.
  - object — OAuth URL to redirect user to
    - `authUrl` string, uri
    - `state` string

## Other responses

- `400` — Platform doesn't support ads, missing accountId for X Ads, or a non-absolute redirect_url
- `401` — Unauthorized
- `403` — Ads access required (Ads add-on on legacy plans, included on usage-based plans), or no access to profile
- `404` — Profile or posting account not found
- `409` — Reconnect a system-user connection with loginMode=business.
- `503` — Business login is not configured or the platform is temporarily unavailable.

## Changes

- **2026-09-17** `be448f13ecdc` — 1 info
  - added the new optional `query` request parameter `permissionLevel`
- **2026-09-10** `e70ed06e7150` — 5 info
  - added the new optional `query` request parameter `loginMode`
  - added the new optional `query` request parameter `pageId`
  - added the non-success response with the status `409`
  - added the non-success response with the status `503`
  - …1 more

[Change history](https://skmtc.dev/zernio/apis/zernio-api/changes/v1/connect/:platform/ads/get.md)

---

[API](https://skmtc.dev/zernio/apis/zernio-api.md) · [All operations](https://skmtc.dev/zernio/apis/zernio-api/llms.txt) · [OpenAPI document](https://skmtc.dev/zernio/apis/zernio-api/revisions/be448f13ecdc?raw)
