REST API · Bridge
Create bridge transfer
Prepare a Bridge transfer WITHOUT transferring anything: the recap and the transfer id
POSThttps://api.apogee.ad/api/v1/bridge-transfers
What it does
Cross-platform: source ads (or a Meta ad set / campaign = its ads, 50 ads max) into a destination campaign, in an existing ad set / TikTok ad group / Snapchat ad squad (destination.adset_id) or a new one copied from a template (destination.new_adset). Meta → Meta (both platforms meta, two ad accounts): ads, ad sets or campaigns. ready false = what to give (recap.missing → extra_fields; recap.fields → mapping), then prepare it again. Show the recap, confirm after the yes.
Request
POST /bridge-transfers on https://api.apogee.ad/api/v1.
Headers
-
Authorizationstring header requiredBearer apk_…: an API key with the scope of this route. -
Idempotency-Keystring header requiredA unique key per request, e.g. a UUID, reused when you retry it: the first answer comes back instead of a second write (idempotency).
-
Content-Typestring header requiredapplication/json
Body
-
sourceobject requiredWhere the ads come from: one of ad_ids, adset_ids, campaign_ids
Fields (5)
-
platformstring required -
ad_account_idstring requiredThe ad account (list_ad_accounts): Meta act_…, a TikTok advertiser id, a Snapchat ad account id
-
ad_idsarray of strings optionalThe ads to transfer (cross-platform: 50 max per run)
-
adset_idsarray of strings optionalMeta: ad sets (cross-platform: their ads; Meta → Meta: the ad sets themselves, 20 max)
-
campaign_idsarray of strings optionalMeta: campaigns (cross-platform: their ads; Meta → Meta: the campaigns themselves, 5 max)
-
-
destinationobject requiredFields (5)
-
platformstring required -
ad_account_idstring requiredThe ad account (list_ad_accounts): Meta act_…, a TikTok advertiser id, a Snapchat ad account id
-
campaign_idstring optionalRequired, except for a Meta → Meta copy of campaigns
-
adset_idstring optionalAn existing destination ad set / TikTok ad group / Snapchat ad squad (list_adsets)
-
new_adsetobject optionalCross-platform: a new ad set copied from a template, instead of adset_id
Fields (3)
-
template_adset_idstring requiredAn ad set / ad group / ad squad of the destination campaign whose settings the new one copies
-
namestring required -
daily_budgetnumber optionalAccount currency; not for a TikTok CBO campaign
-
-
-
launch_statusstring optionalCross-platform. Default CREATED_PAUSED; SCHEDULED_* only with new_adset (+ schedule)
-
scheduleobject optionalA new ad set's schedule, in the destination account's time zone
Fields (4)
-
datestring required -
timestring required -
end_datestring optional -
end_timestring optional
-
-
extra_fieldsobject optionalWhat the plan asked for (recap.missing): the destination's identity / profile / page, URL, CTA, headline
Fields (8)
-
identity_idstring optionalTikTok: the identity the ads run under
-
identity_typestring optionalTikTok: one of the brand's TikTok accounts, TT_USER or BC_AUTH_TT through its Business Center (TikTok refuses a custom identity on these ads)
-
identity_bc_idstring optionalTikTok BC_AUTH_TT: the Business Center
-
profile_idstring optionalSnapchat: the public profile
-
page_idstring optionalMeta: the Facebook page
-
website_urlstring optional -
call_to_actionstring optional -
headlinestring optionalSnapchat: 34 characters
-
-
meta_textsmap optionalDestination Meta: each source ad's texts (by source ad id), instead of the ones the plan suggested (recap.meta_texts)
Fields (3)
-
primary_textstring optional -
headlinestring optional -
descriptionstring optional
-
-
optionsobject optionalMeta → Meta copy only
Fields (3)
-
keep_social_proofboolean optionalDefault true: reuse the source post (likes, comments) when Meta allows it
-
statusstring optionalAfter the copy: PAUSED (default), ACTIVE, or INHERIT (on only what is on in the source)
-
name_suffixstring optional
-
-
mappingmap optionalMeta → Meta copy: values for the plan's fields (recap.fields: pixel, page, audience, budget…), by key
Response
201.
-
idstring -
statusstring -
readyboolean -
recapobject -
askstringWhen not ready: what to give, then prepare it again
-
expires_atstring (date-time)
Every answer carries X-Request-Id (give it to support), Apogee-Version: 2026-10-06, and the RateLimit and RateLimit-Policy headers; a replayed answer also carries Idempotent-Replayed: true.
Errors
Errors are application/problem+json with a stable code to switch on (all the codes).
| Status | Possible codes |
|---|---|
| 400 Bad request | idempotency_key_required, not_supported, request_refused, validation_failed |
| 401 Unauthorized | invalid_api_key |
| 403 Forbidden | account_not_connected, forbidden, insufficient_scope, meta_permission_denied, platform_permission_denied, upgrade_required |
| 404 Not found | not_found |
| 409 Conflict | action_already_ran, idempotency_in_progress, plan_already_launched |
| 412 Precondition failed | drive_not_connected, meta_not_connected, platform_not_connected |
| 422 Unprocessable content | budget_above_cap, idempotency_key_reused, launch_refused, missing_answers |
| 429 Too many requests | daily_read_cap, meta_rate_limited, platform_rate_limited, quota_exceeded, rate_limited |
| 500 Internal server error | internal_error |
| 502 Bad gateway | drive_error, launch_failed, meta_error, platform_error, upstream_error |
Example
With your key in APOGEE_API_KEY (create one):
curl -X POST 'https://api.apogee.ad/api/v1/bridge-transfers' \
-H "Authorization: Bearer $APOGEE_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"source": {
"platform": "meta",
"ad_account_id": "act_1234567890",
"ad_ids": [
"120200000000000003"
]
},
"destination": {
"platform": "tiktok",
"ad_account_id": "7000000000000000001",
"campaign_id": "1800000000000000001",
"adset_id": "1800000000000000002"
}
}' {
"id": "120200000000000001",
"status": "prepared",
"ready": false,
"recap": {},
"ask": "120200000000000001",
"expires_at": "2026-10-23T09:00:00.000Z"
} Access and cost
| Plans | Pro and Scale. |
|---|---|
| Scope | launch (scopes). |
| Credits | Free up to the daily free reads (5,000 a day on Pro, 10,000 on Scale), then 1 credit per read. A request that fails costs nothing, a replayed one neither (credits). |
| Rate limit | Write: 120 per 15 minutes on Pro, 240 on Scale (rate limits). |
| Meta budget | Reads Meta: spends 1 unit of the account's Meta budget (1,800 per 15 minutes, 900 per ad account). |
| Daily cap | Counts 10 units toward the 2,000 units one API key can use in 24 hours. |
In the MCP
An AI assistant does the same with this tool of the MCP server:
prepare_bridge_transfer: Prepares a transfer without transferring anything: the source ads (or a Meta ad set's or campaign's ads), the destination campaign and its existing ad set / ad group / ad squad or a new one copied from a template. Returns the recap and aplan_id;ready: falselists what to ask (a TikTok identity, a Snapchat public profile, a website…), then prepare again withextra_fields. Incompatibilities are listed: your yes accepts them. For a Meta → Meta copy, the destination's fields go inmapping.
Related operations
- Confirm bridge transfer:
POST /bridge-transfers/{id}/confirm - Get bridge transfer:
GET /bridge-transfers/{id} - List bridged ads:
GET /bridged-ads - Get bridge stats:
GET /bridged-ads/stats - Get bridge daily stats:
GET /bridged-ads/daily-stats