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.
/v1/projects/{projectId}/ads/ad-accounts/oauth-url- 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.
projectIdstring (UUID)requiredProject the new ad account will be attached to.
platformstringrequiredWhich platform to connect.One of:meta_ads,tiktok_ads,apple_adsredirectUrlstring (URL)requiredWhere to send the user after they complete (or reject) the OAuth flow. Must be HTTPS and match an allowed origin registered on your Partner credentials.statestringoptionalOpaque 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_01HX9Y7K8M2P4RSTUV56789AB/ads/ad-accounts/oauth-url \
-H "Authorization: Bearer lp_live_01HX9Y6K7EJ4T2_4QZpN..." \
-H "Content-Type: application/json" \
-d '{
"platform": "meta_ads",
"redirectUrl": "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",
redirectUrl: "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",
"redirectUrl": "https://app.example.com/connect/meta/complete",
"state": str(uuid.uuid4()),
},
)
payload = r.json()Response
{
"url": "https://www.facebook.com/v19.0/dialog/oauth?client_id=...&redirect_uri=...&state=...",
"expiresAt": "2026-04-18T19:32:00Z",
"platform": "meta_ads"
}{
"error": {
"code": "RETURN_URL_NOT_ALLOWED",
"message": "returnUrl rejected — api_keys.scope.allowedReturnDomains is empty. Add the FQDN in ladmin and retry.",
"requestId": "req_..."
}
}{
"error": {
"code": "VALIDATION",
"message": "Invalid oauth-url body.",
"requestId": "req_..."
}
}Redirect flow
- Your user clicks "Connect Meta Ads" in your UI.
- You call this endpoint and receive
url. - You redirect the user to
url. - The user grants access on the platform.
- The platform redirects to a Layers-hosted callback (
/v1/ads/oauth-callback/{platform}). - Layers completes the token exchange server-side, creates the credential row, and 302s the browser to your
redirectUrl(with?layers_oauth_error=<code>only on failure — the success path 302s clean). - Poll
GET /v1/projects/:projectId/ads/ad-accounts?platforms={platform}until the new row appears withtokenStatus: "valid". Usually under 5 seconds.
Your state is stored alongside the OAuth state row as metadata.partnerState so you can correlate the callback back to the originating session if you keep your own bookkeeping. It is not echoed in the redirect query string.
Scope Layers requests
| Platform | Scopes |
|---|---|
meta_ads | ads_management, ads_read, business_management, pages_manage_ads, pages_show_list |
tiktok_ads | user.info.basic, advertiser.list, ad.read, ad.write |
apple_ads | campaigns:read, campaigns:write |
If the user denies any required scope, the redirect still fires and Layers creates no ad account. You will see no new row on poll — that is your signal the user rejected.
Notes
urlis single-use and expires in 10 minutes. Create a fresh one if the user abandons and comes back later.- Reconnecting an expired account uses the same endpoint — the resulting row updates the existing
ad_accountsentry in place rather than creating a new one. redirectUrlorigin must be pre-registered on your Partner credentials. Add origins in the Layers dashboard → Partner → Redirect origins.- There is no "connect and immediately use" shortcut. Wait for
tokenStatus: "valid"before making campaign/ads calls against the new ad account.
See also
GET /v1/projects/:projectId/ads/ad-accounts— poll for the new account- Onboard a customer — full connection walkthrough
- Authentication — Partner keys, redirect origins, scopes