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
| Method | Path | Description | Status |
|---|---|---|---|
| POST | /api/v1/oauth/tickets | Create OAuth ticket | 201 |
| GET | /api/v1/oauth/{platform}/authorize | Validate ticket, redirect to consent screen | 302 |
| GET | /api/v1/oauth/{platform}/callback | Exchange code, create connection, redirect back | 302 |
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
| Field | Type | Required | Description |
|---|---|---|---|
platform | string | Yes | Target platform: youtube or facebook |
redirectUrl | string | Yes | URL the user is sent to after OAuth completes. The connectionId and status are appended as query parameters. |
connectionId | UUID | No | Pass an existing connection's UUID to re-authorize it (e.g. after token expiry). Omit to create a new connection. |
organizationId | UUID | No | Associate 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
| Field | Type | Description |
|---|---|---|
ticketId | UUID | Unique ID of the ticket (embedded in authUrl) |
authUrl | string | Full URL to redirect the user to — includes the ticket as a query parameter |
expiresAt | ISO 8601 | When 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.
| Parameter | Location | Description |
|---|---|---|
platform | Path | youtube or facebook |
ticket | Query | Ticket 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:
- Exchanges the code for access and refresh tokens
- Creates a new Connection (or updates the existing one if
connectionIdwas in the ticket) - Issues a
302redirect to yourredirectUrl
Redirect URL parameters
On success, the user arrives at your redirectUrl with these query parameters appended:
| Parameter | Description |
|---|---|
connectionId | UUID of the newly created or updated connection |
status | success |
https://your-app.com/channels/connected?connectionId=7f3e1a2b-4c5d-6e7f-8a9b-0c1d2e3f4a5b&status=successOn failure (user denied consent, ticket expired, etc.), status=error is appended instead:
https://your-app.com/channels/connected?status=error&error=access_deniedHandle 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:
| Status | Reason |
|---|---|
400 | Missing required field or invalid platform value |
401 | Missing or invalid API key |
404 | connectionId 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 value | Meaning |
|---|---|
access_denied | User declined the consent screen |
ticket_expired | The 5-minute ticket window elapsed before consent was completed |
invalid_ticket | Ticket was already used or does not exist |
platform_error | Unexpected 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
redirectUrlmust 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
- Connecting Channels guide — end-to-end walkthrough with redirect handler examples
- Connections — manage, inspect, and deactivate connections after OAuth
- Authentication — API key authentication and JWT for console access
- Assignments — publish content using a
connectionIdfrom a connection