POST /v1/projects/:projectId/social/oauth-url
Create a platform OAuth URL so an end-customer can connect their TikTok or Instagram account. You control the returnUrl.
/v1/projects/:projectId/social/oauth-url- Auth
- Bearer
- Scope
- social:write
Create an OAuth authorization URL for TikTok or Instagram. The end-customer opens it in a top-level browser tab, consents on the platform's domain, and is redirected back to a returnUrl you control — not to Layers.
You pass returnUrl per call. It must match an allowlist on your API key; anything else is rejected with RETURN_URL_NOT_ALLOWED. Layers never re-hosts the consent page, and the URL cannot be framed (TikTok and Instagram block their domains from iframes by ToS). Your UI opens it in a new tab or redirects the user to it directly.
The authorize URL lives on tiktok.com or instagram.com. It will not render inside an iframe. Open it in a top-level tab, then poll GET /v1/social/oauth-status/:state until it flips to completed. The status endpoint is not project-scoped — the state token uniquely identifies the attempt and is scoped by API key.
projectIdstringrequiredProject that will own the resulting social account.
platformstringrequiredTarget platform.One of:tiktok,instagramreturnUrlstring (URL)requiredWhere Layers redirects the end-customer after consent. Must match an entry in the key's return_url_allowlist.scopesstring[]optionalPlatform scopes to request. Defaults to the full set Layers needs for publishing and metrics.usageNotestringoptionalShown to the user on your side after the callback (if you render one). Opaque to Layers.
Example request
curl https://api.layers.com/v1/projects/prj_01HX9Y7K8M2P4RSTUV56789AB/social/oauth-url \
-H "Authorization: Bearer lp_live_01HX9Y6K7EJ4T2_4QZpN..." \
-H "Content-Type: application/json" \
-d '{
"platform": "tiktok",
"returnUrl": "https://app.gicgrowth.com/connect/tiktok/return",
"usageNote": "Connecting Acme Coffee"
}'const { authorizeUrl, state } = await layers.social.createOAuthUrl({
projectId: "prj_01HX9Y7K8M2P4RSTUV56789AB",
platform: "tiktok",
returnUrl: "https://app.gicgrowth.com/connect/tiktok/return",
usageNote: "Connecting Acme Coffee",
});
window.location.assign(authorizeUrl);result = layers.social.create_oauth_url(
project_id="prj_01HX9Y7K8M2P4RSTUV56789AB",
platform="tiktok",
return_url="https://app.gicgrowth.com/connect/tiktok/return",
usage_note="Connecting Acme Coffee",
)
# send result["authorizeUrl"] to the end-customer's browserResponse
{
"authorizeUrl": "https://www.tiktok.com/v2/auth/authorize?client_key=...&state=st_01HXZ8...",
"state": "st_01HXZ8K2M4P5QRS6TUV7WXYZ9A",
"expiresAt": "2026-04-18T19:12:11Z"
}The returned state is a Layers-created opaque token — do not reuse or modify it. When the end-customer returns to your returnUrl, Layers appends ?layers_oauth_error=<code> only on failure (the success case carries no extra params — Layers has already persisted the social account). Pass the same state to GET /v1/social/oauth-status/:state to learn the resulting socialAccountId.
The authorize URL expires in 10 minutes. After expiry, create a new one — do not cache.
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION | platform not recognized, returnUrl not an absolute URL. |
| 401 | UNAUTHENTICATED | Missing or invalid key. |
| 403 | FORBIDDEN_SCOPE | Key lacks social:write. |
| 403 | RETURN_URL_NOT_ALLOWED | returnUrl not in the key's return_url_allowlist. Add it via ladmin, then retry. |
| 404 | NOT_FOUND | Project not in your organization. |
| 429 | RATE_LIMITED | Write budget exhausted. |
See also
GET /v1/social/oauth-status/:state— poll completion (not project-scoped)GET /v1/projects/:projectId/social-accounts— list connected accountsPOST /v1/projects/:projectId/social/reauth-url— refresh an existing connection- Social accounts — how Layers stores and refreshes tokens