Create connection
Create an Airbyte source for a client and wire it to the agency's BigQuery destination. The client_id must belong to the calling agency and the agency workspace must already be provisioned and active. Requires an agency key (ak_).
Streams are reconciled against the source's real catalog. Omit streams and you get the platform's defaults, filtered to what this source actually offers — a default a connector has since renamed is dropped rather than failing your whole connection. Supply streams yourself and any name the source does not offer is rejected with 400 unknown_streams, listing the valid names, because silently dropping a stream you asked for would lose data you expect. Stream names are the CONNECTOR's, not the ad platform's UI labels — Meta's "Ad sets" is the stream ad_sets.
curl -X POST "https://mythic-analytics.gulp.workers.dev/client/v1/airbyte/connections" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"client_id": "acme-retail",
"platform": "google_ads",
"display_name": "Acme — Google Ads",
"account_id": "123-456-7890",
"source_config": {},
"secret_id": "example_string",
"sync_frequency": "manual",
"sync_hour_utc": 8,
"streams": [
{
"name": "campaigns",
"syncMode": "incremental_deduped_history",
"cursorField": "segments.date",
"sourceDefinedCursor": true,
"primaryKey": [
[
"example_string"
]
]
}
]
}'
import requests
import json
url = "https://mythic-analytics.gulp.workers.dev/client/v1/airbyte/connections"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"client_id": "acme-retail",
"platform": "google_ads",
"display_name": "Acme — Google Ads",
"account_id": "123-456-7890",
"source_config": {},
"secret_id": "example_string",
"sync_frequency": "manual",
"sync_hour_utc": 8,
"streams": [
{
"name": "campaigns",
"syncMode": "incremental_deduped_history",
"cursorField": "segments.date",
"sourceDefinedCursor": true,
"primaryKey": [
[
"example_string"
]
]
}
]
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://mythic-analytics.gulp.workers.dev/client/v1/airbyte/connections", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"client_id": "acme-retail",
"platform": "google_ads",
"display_name": "Acme — Google Ads",
"account_id": "123-456-7890",
"source_config": {},
"secret_id": "example_string",
"sync_frequency": "manual",
"sync_hour_utc": 8,
"streams": [
{
"name": "campaigns",
"syncMode": "incremental_deduped_history",
"cursorField": "segments.date",
"sourceDefinedCursor": true,
"primaryKey": [
[
"example_string"
]
]
}
]
})
});
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": "google_ads",
"display_name": "Acme — Google Ads",
"account_id": "123-456-7890",
"source_config": {},
"secret_id": "example_string",
"sync_frequency": "manual",
"sync_hour_utc": 8,
"streams": [
{
"name": "campaigns",
"syncMode": "incremental_deduped_history",
"cursorField": "segments.date",
"sourceDefinedCursor": true,
"primaryKey": [
[
"example_string"
]
]
}
]
}`)
req, err := http.NewRequest("POST", "https://mythic-analytics.gulp.workers.dev/client/v1/airbyte/connections", 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/connections')
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": "google_ads",
"display_name": "Acme — Google Ads",
"account_id": "123-456-7890",
"source_config": {},
"secret_id": "example_string",
"sync_frequency": "manual",
"sync_hour_utc": 8,
"streams": [
{
"name": "campaigns",
"syncMode": "incremental_deduped_history",
"cursorField": "segments.date",
"sourceDefinedCursor": true,
"primaryKey": [
[
"example_string"
]
]
}
]
}'
response = http.request(request)
puts response.body
{
"success": true,
"data": {
"id": "example_string",
"client_id": "acme-retail",
"agency_id": "example_string",
"platform": "google_ads",
"connection_id": "example_string",
"source_id": "example_string",
"display_name": "Google Ads - acme-retail",
"account_id": "123-456-7890",
"status": "active",
"sync_frequency": "manual",
"sync_hour_utc": 8,
"sync_dow": 1,
"tables_json": [
{}
],
"companions": [
{}
],
"pipeline": {
"enrolled": true,
"reason": "example_string",
"pipeline_id": "example_string",
"connection_ids": [
"example_string"
],
"pipeline_enabled": true
},
"last_sync_at": "2024-12-25T10:00:00Z",
"last_sync": {
"job_id": 123,
"success": true,
"landed": true,
"finished_at": "2024-12-25T10:00:00Z",
"started_at": "2024-12-25T10:00:00Z",
"duration_seconds": 42,
"records_committed": 42,
"records_emitted": 42,
"records_filtered_out": 42,
"bytes_committed": 42,
"error_type": "transient_error",
"error_origin": "destination",
"error_message": "example_string",
"log_url": "example_string"
},
"consecutive_failures": 0,
"error_message": "example_string",
"created_at": "2024-12-25T10:00:00Z"
}
}
{
"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
}
/connections
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 from the platforms catalog.
Human-readable connection name. Defaults to <platform> - <client_id>.
Platform account identifier. Falls back to the platform's account-ID field within source_config if omitted. NULL for connectors that have no scalar account id — bing_ads selects accounts with account_names predicates, and tiktok_ads has no advertiser id in its OAuth flow at all. Do not rely on this being populated for every platform.
Platform-specific source configuration, matching the platform's requiredFields. When secret_id is supplied, omit the fields flagged oauth: true — Airbyte injects those credentials.
Credential handle from a completed OAuth handshake, in place of raw credentials in source_config. Normally you never set this by hand: /oauth/callback/{state} creates the connection for you. It exists for callers that want to run the consent step themselves and create the connection later.
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 when omitted.
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-retailHuman-readable connection name. Defaults to \\<platform\\> - \\<client_id\\>.
Acme — Google AdsPlatform account identifier. Falls back to the platform's account-ID field within source_config if omitted. NULL for connectors that have no scalar account id — bing_ads selects accounts with account_names predicates, and tiktok_ads has no advertiser id in its OAuth flow at all. Do not rely on this being populated for every platform.
123-456-7890Platform-specific source configuration, matching the platform's requiredFields. When secret_id is supplied, omit the fields flagged oauth: true — Airbyte injects those credentials.
Credential handle from a completed OAuth handshake, in place of raw credentials in source_config. Normally you never set this by hand: /oauth/callback/\\{state\\} creates the connection for you. It exists for callers that want to run the consent step themselves and create the connection later.
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.
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 when omitted.