Start an OAuth connection
Begin a provider OAuth handshake for a platform whose catalog entry has supportsOauth: true — which means the provider CREDENTIAL comes from consent, not that there is nothing to fill in; see manualFields for what you must still pass. Returns a consent_url to redirect the browser to; after the user consents the provider redirects back to /oauth/callback/{state}, which creates the source and connection and then bounces to return_url.
No long-lived access or refresh token is ever handled by the caller or stored by Mythic — Airbyte holds the tokens and refreshes them. Fields the consent flow supplies are flagged oauth: true in the platform catalog; pass the remaining non-secret fields (account identifiers, Bing's developer_token) in source_config, since they are needed to create the source on the callback. Account identifiers are normalised for you — Meta's act_ prefix is stripped and accepted as account_id or account_ids, and dashes are stripped from a Google customer_id.
meta_ads and google_ads REQUIRE an account identifier here, before consent (missing_account_id otherwise): absent, the connect fails after the user has already authorised, as an opaque catalog-discovery error.
return_url must have an origin the agency registered via PATCH /config — see oauth_return_origins. An unregistered origin is rejected with return_url_not_allowed at initiation, so it is never stored, and the callback can never be used to bounce a browser to an arbitrary destination.
The request is validated before the provisioning gate. A malformed request gets its own code — missing_account_id, return_url_not_allowed, unsupported_platform — whether or not the agency has provisioned yet, so you can wire up and verify the consent call against an unprovisioned agency and still get an actionable reason. not_provisioned means the request itself was fine.
The pending request is held for 15 minutes and the returned state is single-use. It is also your correlation handle: hold it against the connection you are creating, since the connection id does not exist until the callback completes. Rate limited to 20 initiations per 10 minutes per agency. Requires an agency key (ak_).
curl -X POST "https://mythic-analytics.gulp.workers.dev/client/v1/airbyte/oauth/initiate" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"client_id": "acme-retail",
"platform": "meta_ads",
"return_url": "https://portal.example.com/integrations/acme-retail",
"source_config": {},
"display_name": "Acme — Meta Ads",
"account_id": "act_123456789",
"sync_frequency": "manual",
"sync_hour_utc": 8,
"streams": [
{
"name": "campaigns",
"syncMode": "incremental_deduped_history",
"cursorField": "segments.date",
"sourceDefinedCursor": true,
"primaryKey": [
[
"example_string"
]
]
}
],
"oauth_input_configuration": {}
}'
import requests
import json
url = "https://mythic-analytics.gulp.workers.dev/client/v1/airbyte/oauth/initiate"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"client_id": "acme-retail",
"platform": "meta_ads",
"return_url": "https://portal.example.com/integrations/acme-retail",
"source_config": {},
"display_name": "Acme — Meta Ads",
"account_id": "act_123456789",
"sync_frequency": "manual",
"sync_hour_utc": 8,
"streams": [
{
"name": "campaigns",
"syncMode": "incremental_deduped_history",
"cursorField": "segments.date",
"sourceDefinedCursor": true,
"primaryKey": [
[
"example_string"
]
]
}
],
"oauth_input_configuration": {}
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://mythic-analytics.gulp.workers.dev/client/v1/airbyte/oauth/initiate", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"client_id": "acme-retail",
"platform": "meta_ads",
"return_url": "https://portal.example.com/integrations/acme-retail",
"source_config": {},
"display_name": "Acme — Meta Ads",
"account_id": "act_123456789",
"sync_frequency": "manual",
"sync_hour_utc": 8,
"streams": [
{
"name": "campaigns",
"syncMode": "incremental_deduped_history",
"cursorField": "segments.date",
"sourceDefinedCursor": true,
"primaryKey": [
[
"example_string"
]
]
}
],
"oauth_input_configuration": {}
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"client_id": "acme-retail",
"platform": "meta_ads",
"return_url": "https://portal.example.com/integrations/acme-retail",
"source_config": {},
"display_name": "Acme — Meta Ads",
"account_id": "act_123456789",
"sync_frequency": "manual",
"sync_hour_utc": 8,
"streams": [
{
"name": "campaigns",
"syncMode": "incremental_deduped_history",
"cursorField": "segments.date",
"sourceDefinedCursor": true,
"primaryKey": [
[
"example_string"
]
]
}
],
"oauth_input_configuration": {}
}`)
req, err := http.NewRequest("POST", "https://mythic-analytics.gulp.workers.dev/client/v1/airbyte/oauth/initiate", bytes.NewBuffer(data))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
require 'net/http'
require 'json'
uri = URI('https://mythic-analytics.gulp.workers.dev/client/v1/airbyte/oauth/initiate')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request.body = '{
"client_id": "acme-retail",
"platform": "meta_ads",
"return_url": "https://portal.example.com/integrations/acme-retail",
"source_config": {},
"display_name": "Acme — Meta Ads",
"account_id": "act_123456789",
"sync_frequency": "manual",
"sync_hour_utc": 8,
"streams": [
{
"name": "campaigns",
"syncMode": "incremental_deduped_history",
"cursorField": "segments.date",
"sourceDefinedCursor": true,
"primaryKey": [
[
"example_string"
]
]
}
],
"oauth_input_configuration": {}
}'
response = http.request(request)
puts response.body
{
"success": true,
"data": {
"consent_url": "https://www.facebook.com/v18.0/dialog/oauth?client_id=…",
"state": "example_string",
"expires_in": 900
}
}
{
"error": "Bad Request",
"message": "The request contains invalid parameters or malformed data",
"code": 400,
"details": [
{
"field": "email",
"message": "Invalid email format"
}
]
}
{
"error": "Unauthorized",
"message": "Authentication required. Please provide a valid API token",
"code": 401
}
{
"error": "Forbidden",
"message": "You don't have permission to access this resource",
"code": 403
}
{
"error": "Too Many Requests",
"message": "Rate limit exceeded. Please try again later",
"code": 429,
"retryAfter": 3600
}
/oauth/initiate
Target server for requests. Edit to use your own host.
Agency key as bearer token, format Bearer ak_.... Grants full read-write access scoped to the agency. Agency-wide scoped keys (mcp_ with no fixed location) are accepted too and need airbyte:read or airbyte:write; a client-bound mcp_ key gets 403 agency_key_required. See Using an mcp_ key over HTTP.
Bearer ak_.... Grants full read-write access scoped to the agency. Agency-wide scoped keys (mcp_ with no fixed location) are accepted too and need airbyte:read or airbyte:write; a client-bound mcp_ key gets 403 agency_key_required. See Using an mcp_ key over HTTP.
Location secret key as bearer token, format Bearer sk_.... Grants read-only access; the agency is resolved from the location. Write endpoints return 403.
Bearer sk_.... Grants read-only access; the agency is resolved from the location. Write endpoints return 403.
The media type of the request body
Client (location) identifier. Must belong to the calling agency.
Platform key whose catalog entry has supportsOauth: true.
Absolute http(s) URL to send the browser to once the connection is created. Its ORIGIN must be registered in oauth_return_origins (PATCH /config) or the request is rejected with return_url_not_allowed. mythic_oauth plus mythic_connection_id or mythic_error_code are appended; your own query parameters are kept. Omit it and the callback renders a terminal HTML page instead.
Non-secret connector fields needed to create the source after consent — everything in the platform's requiredFields NOT flagged oauth: true. Account identifiers are normalised, so { "account_id": "act_123" } and { "account_ids": ["123"] } are equally acceptable for Meta, and a dashed Google customer_id is fine. Required for meta_ads and google_ads.
Sync cadence. 24h, 12h and 6h schedule automatic syncs at sync_hour_utc; manual disables them, and is the DEFAULT — a connection created without this field does not sync on a schedule.
Hour of day, UTC, the cadence is anchored to. Defaults to 2 when unset. Sub-daily cadences anchor on hour % interval, so the hour you ask for is always one of the run times and the runs stay evenly spaced — 6h at 8 runs at 02:00, 08:00, 14:00 and 20:00. Ignored when sync_frequency is manual.
Stream selection. Defaults to the platform's defaultStreams.
Optional provider-specific OAuth inputs passed through to Airbyte (oAuthInputConfiguration), for connectors that need one to build the consent URL.
Request Preview
Response
Response will appear here after sending the request
Authentication
Bearer token. Agency key as bearer token, format Bearer ak_.... Grants full read-write access scoped to the agency. Agency-wide scoped keys (mcp_ with no fixed location) are accepted too and need airbyte:read or airbyte:write; a client-bound mcp_ key gets 403 agency_key_required. See Using an mcp_ key over HTTP.
Bearer token. Location secret key as bearer token, format Bearer sk_.... Grants read-only access; the agency is resolved from the location. Write endpoints return 403.
Body
Client (location) identifier. Must belong to the calling agency.
acme-retailAbsolute http(s) URL to send the browser to once the connection is created. Its ORIGIN must be registered in oauth_return_origins (PATCH /config) or the request is rejected with return_url_not_allowed. mythic_oauth plus mythic_connection_id or mythic_error_code are appended; your own query parameters are kept. Omit it and the callback renders a terminal HTML page instead.
https://portal.example.com/integrations/acme-retailNon-secret connector fields needed to create the source after consent — everything in the platform's requiredFields NOT flagged oauth: true. Account identifiers are normalised, so \\{ "account_id": "act_123" \\} and \\{ "account_ids": ["123"] \\} are equally acceptable for Meta, and a dashed Google customer_id is fine. Required for meta_ads and google_ads.
Acme — Meta Adsact_123456789Sync cadence. 24h, 12h and 6h schedule automatic syncs at sync_hour_utc; manual disables them, and is the DEFAULT — a connection created without this field does not sync on a schedule.
manual24h12h6hHour of day, UTC, the cadence is anchored to. Defaults to 2 when unset. Sub-daily cadences anchor on hour % interval, so the hour you ask for is always one of the run times and the runs stay evenly spaced — 6h at 8 runs at 02:00, 08:00, 14:00 and 20:00. Ignored when sync_frequency is manual.
8Stream selection. Defaults to the platform's defaultStreams.
Optional provider-specific OAuth inputs passed through to Airbyte (oAuthInputConfiguration), for connectors that need one to build the consent URL.