API Reference
OAuth / Channel Connect

OAuth / Channel Connect

Ticket-based OAuth flow for connecting YouTube channels and Facebook pages to your workspace. Once connected, each channel becomes a Connection that you can publish assignments to.

For a step-by-step integration walkthrough, see the Connecting Channels guide. To manage existing connections after OAuth completes, see Connections.

Base URL: https://pharlo.io
Auth: Authorization: Bearer ds_live_...


How the flow works

The Pharlo uses a ticket-based OAuth approach rather than a direct redirect from your frontend. This keeps your API key server-side and prevents it from being exposed to the browser.

Your backend              Pharlo           Google / Meta
     │                          │                       │
     │── POST /oauth/tickets ──►│                       │
     │◄── { authUrl, ticketId } ─                       │
     │                          │                       │
     │  (redirect user to authUrl)                      │
     │                          │──── /authorize ──────►│
     │                          │   (consent screen)    │
     │                          │◄── /callback ─────────│
     │                          │  (create Connection)  │
     │                          │                       │
     │  (user lands on your redirectUrl)                │
     │── GET /connections ──────►                       │
     │◄── [ new connection ] ────                       │

Create an OAuth ticket

Your backend calls POST /api/v1/oauth/tickets. The response contains an authUrl.

Redirect the user

Send the user's browser to authUrl. They see the Google or Meta consent screen. You do not interact with this step — it happens in the browser.

OAuth completes

After the user grants permissions, the Pharlo creates (or updates) a Connection and redirects the user to your redirectUrl. The new connectionId is appended as a query parameter.

Use the connection

Fetch the new connection from Connections and use its id as connectionId in assignment requests.


Endpoints

MethodPathDescriptionStatus
POST/api/v1/oauth/ticketsCreate OAuth ticket201
GET/api/v1/oauth/{platform}/authorizeValidate ticket, redirect to consent screen302
GET/api/v1/oauth/{platform}/callbackExchange code, create connection, redirect back302

POST /api/v1/oauth/tickets

Creates a short-lived ticket that authorizes the upcoming OAuth redirect. Always start here — your backend must call this before redirecting the user.

⚠️

Tickets expire in 5 minutes and are single-use. If the user takes too long or the flow fails, create a new ticket and restart.

Request body

FieldTypeRequiredDescription
platformstringYesTarget platform: youtube or facebook
redirectUrlstringYesURL the user is sent to after OAuth completes. The connectionId and status are appended as query parameters.
connectionIdUUIDNoPass an existing connection's UUID to re-authorize it (e.g. after token expiry). Omit to create a new connection.
organizationIdUUIDNoAssociate the new connection with a specific organization in your workspace. Defaults to the workspace root.

Example request

curl -X POST https://pharlo.io/api/v1/oauth/tickets \
  -H "Authorization: Bearer $DELIVERY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "youtube",
    "redirectUrl": "https://your-app.com/channels/connected"
  }'

Response 201 Created

FieldTypeDescription
ticketIdUUIDUnique ID of the ticket (embedded in authUrl)
authUrlstringFull URL to redirect the user to — includes the ticket as a query parameter
expiresAtISO 8601When the ticket expires (5 minutes from creation)
{
  "ticketId": "550e8400-e29b-41d4-a716-446655440000",
  "authUrl": "https://pharlo.io/api/v1/oauth/youtube/authorize?ticket=550e8400-e29b-41d4-a716-446655440000",
  "expiresAt": "2026-04-23T12:05:00Z"
}

Redirect the user's browser to authUrl. Do not call authUrl from your backend — it must open in the user's browser so they can see the consent screen.


GET /api/v1/oauth/{platform}/authorize

Browser-only endpoint. You do not call this directly — the user's browser follows the authUrl from the ticket response. It validates the ticket and issues a 302 redirect to the Google or Meta consent screen.

ParameterLocationDescription
platformPathyoutube or facebook
ticketQueryTicket ID from POST /oauth/tickets

This endpoint returns 302 Found. It is not a JSON API — calling it from a server-side HTTP client will not work as intended.


GET /api/v1/oauth/{platform}/callback

Browser-only endpoint. Called automatically by Google or Meta after the user grants (or denies) consent. You do not call this endpoint — register it as a redirect URI in your OAuth app if required by the platform.

After receiving the authorization code, the Pharlo:

  1. Exchanges the code for access and refresh tokens
  2. Creates a new Connection (or updates the existing one if connectionId was in the ticket)
  3. Issues a 302 redirect to your redirectUrl

Redirect URL parameters

On success, the user arrives at your redirectUrl with these query parameters appended:

ParameterDescription
connectionIdUUID of the newly created or updated connection
statussuccess
https://your-app.com/channels/connected?connectionId=7f3e1a2b-4c5d-6e7f-8a9b-0c1d2e3f4a5b&status=success

On failure (user denied consent, ticket expired, etc.), status=error is appended instead:

https://your-app.com/channels/connected?status=error&error=access_denied

Handle both cases in your redirect handler. When status=success, store the connectionId — you'll need it to create assignments. See Connections for the full connection object.


Re-authorizing an existing connection

When a connection's OAuth token expires, assignments to it fail. To re-authorize without creating a duplicate connection, pass the existing connectionId in the ticket request:

curl -X POST https://pharlo.io/api/v1/oauth/tickets \
  -H "Authorization: Bearer $DELIVERY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "youtube",
    "connectionId": "7f3e1a2b-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
    "redirectUrl": "https://your-app.com/channels/reconnected"
  }'

After the user completes consent, the existing connection is updated with fresh tokens. The connectionId in your system stays the same — no need to update your stored references.

You can also attempt a silent token refresh without user interaction via POST /api/v1/connections/{id}/refresh-token. If the refresh token is still valid this avoids the OAuth round-trip entirely. See Connections.


Error responses

Errors from POST /api/v1/oauth/tickets:

StatusReason
400Missing required field or invalid platform value
401Missing or invalid API key
404connectionId not found or does not belong to this workspace
{
  "error": "Validation failed",
  "details": {
    "platform": ["The value 'tiktok' is not valid. Allowed: youtube, facebook."],
    "redirectUrl": ["This value should not be blank."]
  }
}

Errors surfaced to your redirectUrl via query parameter after the user completes (or abandons) the consent screen:

error valueMeaning
access_deniedUser declined the consent screen
ticket_expiredThe 5-minute ticket window elapsed before consent was completed
invalid_ticketTicket was already used or does not exist
platform_errorUnexpected error from Google or Meta

Troubleshooting

User sees an error after the consent screen

  • The ticket may have expired (5-minute window). Create a new ticket and retry.
  • The redirectUrl must be an exact match — path, protocol, and any trailing slashes.

Connection is inactive after creation

  • The OAuth scope granted may have been insufficient. Re-run the full OAuth flow.

Token refresh fails silently

  • Refresh tokens from Google expire after 7 days of inactivity and from Meta after 60–90 days, or when the user revokes app access. Create a new ticket to re-authorize.

YouTube quota exceeded

  • The Google OAuth app may have hit its daily quota. Contact your platform admin to check YouTube quota usage.

See also