Layers
Partner APIAPI referenceAds

POST /v1/projects/:projectId/ads/ad-accounts/oauth-url

Create a one-time OAuth URL for connecting a Meta, TikTok, or Apple ad account to a project.

View as Markdown
POST/v1/projects/{projectId}/ads/ad-accounts/oauth-url
Phase 1stable
Auth
Bearer
Scope
ads:write

Returns a short-lived OAuth URL your user opens to grant Layers access to their ad account. Layers' ad platform integrations are bring-your-own - this is the only path to connect an ad account today. The URL itself is single-use and scoped to one project, one platform, and one user session.

Path
  • projectId
    string (UUID)required
    Project the new ad account will be attached to.
Body
  • platform
    stringrequired
    Which platform to connect.
    One of: meta_ads, tiktok_ads, apple_ads
  • returnUrl
    string (URL)required
    Where to send the user after they complete (or reject) the OAuth flow. Must be HTTPS and match an allowed return URL on your API key. Same field name as `/social/oauth-url`.
  • redirectUrl
    string (URL)optional
    Deprecated alias for `returnUrl`. Accepted for backwards compatibility for one release cycle and then removed. Send `returnUrl` going forward.
  • state
    stringoptional
    Opaque string echoed back in the redirect. Use it to round-trip your own request id.

Example request

curl -X POST https://api.layers.com/v1/projects/prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39/ads/ad-accounts/oauth-url \
  -H "Authorization: Bearer lp_..." \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "meta_ads",
    "returnUrl": "https://app.example.com/connect/meta/complete",
    "state": "sess_9f2a4d1c"
  }'
const res = await fetch(
  `https://api.layers.com/v1/projects/${projectId}/ads/ad-accounts/oauth-url`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      platform: "meta_ads",
      returnUrl: "https://app.example.com/connect/meta/complete",
      state: crypto.randomUUID(),
    }),
  },
);
const { url, expiresAt } = await res.json();
// Redirect the user to `url`.
import httpx, uuid

r = httpx.post(
    f"https://api.layers.com/v1/projects/{project_id}/ads/ad-accounts/oauth-url",
    headers={"Authorization": f"Bearer {api_key}"},
    json={
        "platform": "meta_ads",
        "returnUrl": "https://app.example.com/connect/meta/complete",
        "state": str(uuid.uuid4()),
    },
)
payload = r.json()

Response

200OK - URL created.
{
  "url": "https://www.facebook.com/v19.0/dialog/oauth?client_id=...&redirect_uri=...&state=...",
  "expiresAt": "2026-04-18T19:32:00Z",
  "platform": "meta_ads"
}
403returnUrl host is not on this API key's return-URL allowlist.
{
  "error": {
    "code": "RETURN_URL_NOT_ALLOWED",
    "message": "Return URL is not on this key's allowlist. Contact your Layers account manager to add this domain to the key's allowed return URLs.",
    "requestId": "req_...",
    "details": {
      "returnUrl": "https://app.example.com/connect/meta/complete",
      "host": "app.example.com"
    }
  }
}
422Validation - returnUrl is not a parseable absolute URL, platform missing or unsupported.
{
  "error": {
    "code": "VALIDATION",
    "message": "Invalid oauth-url body.",
    "requestId": "req_..."
  }
}
404Project does not exist in your organization.

Redirect flow

  1. Your user clicks "Connect Meta Ads" in your UI.
  2. You call this endpoint and receive url.
  3. You redirect the user to url.
  4. The user grants access on the platform.
  5. The platform redirects to a Layers-hosted callback (/v1/ads/oauth-callback/{platform}).
  6. Layers completes the token exchange and redirects the browser to your returnUrl (with ?layers_oauth_error=<code> only on failure - the success path redirects cleanly).
  7. Poll GET /v1/projects/:projectId/ads/ad-accounts?platforms={platform} until the account appears with tokenStatus: "valid".

Your state is kept for callback correlation but is not echoed in the redirect query string.

Scope Layers requests

PlatformScopes
meta_adsads_management, ads_read, business_management, pages_manage_ads, pages_show_list
tiktok_adsuser.info.basic, advertiser.list, ad.read, ad.write
apple_adscampaigns:read, campaigns:write

If the user denies any required scope, the redirect still fires and no ad account is connected. You will see no new account on poll - that is your signal the user rejected.

Notes

  • url is single-use and short-lived. Create a fresh one if the user abandons and comes back later.
  • Reconnecting an expired account uses the same endpoint and updates the existing connection rather than creating a new one.
  • returnUrl host must be on the API key's return-URL allowlist. Contact your Layers account manager to add a new domain — partners cannot self-serve the allowlist today. The legacy field name redirectUrl is accepted for one release cycle as a deprecated alias; new integrations should send returnUrl.
  • There is no "connect and immediately use" shortcut. Wait for tokenStatus: "valid" before making campaign/ads calls against the new ad account.

See also

On this page