.dev
REST API

Marketing

Meta (Facebook) Marketing integration — OAuth connect, ad accounts, campaign/adset/ad reads, ad↔product links — and Meta/TikTok pixel server events.

Two marketing surfaces live in the API:

  • Meta integration (store.integrations.meta) — connect a Meta account per store, then read ad accounts, campaigns, ad sets, ads and KPIs, and maintain ad ↔ product links that join ad spend with the Feeef orders those products actually took.
  • Pixel server events — server-side conversion events to Meta Pixel (Conversions API) and TikTok Pixel (Events API), fanned out to every pixel configured on the store.

Connect a Meta account

The OAuth handshake is store-scoped and authenticated end to end. Tokens are exchanged for a long-lived (~60 days) token, stored encrypted server-side, and never returned to clients — config reads expose only an oauth2Connected boolean (see the config envelope).

POST /v1/stores/{storeId}/integrations/meta/oauth/start — optional body { "popup": true, "origin": "…", "callbackScheme": "…" }. Returns { "authUrl": "…" }.

Open authUrl — the merchant consents on facebook.com (scope is ads_read only for now).

Meta redirects to the shared marketing callback, which persists the encrypted token on the store. Web popups get a postMessage; native apps receive a deep link and fetch the outcome once via GET /v1/social/meta/oauth-result?nonce=….

GET /v1/stores/{storeId}/integrations/meta now reports active, the connected account, and capabilities (connected, canRead, canManage, needsReconnect).

Ads endpoints are read-only today: managing delivery status needs the ads_management scope, which is pending Meta App Review. Check capabilities.canManage before offering write UI. A 409 with code META_NOT_CONNECTED / META_TOKEN_EXPIRED means connect (or reconnect) first; 403 META_SCOPE_MISSING means the token lacks ads_read.

Meta integration — at a glance

All routes require a Bearer token plus store-member RBAC (reads: any member; writes: editor/admin/owner).

MethodPathDescriptionAuth
GET/v1/stores/{storeId}/integrations/metaConnection status + capabilities + ads settingsBearer
POST/v1/stores/{storeId}/integrations/meta/oauth/startBegin OAuth, returns authUrlBearer
DELETE/v1/stores/{storeId}/integrations/meta/oauthDisconnect (drops credentials, keeps ad↔product links)Bearer
GET/v1/stores/{storeId}/integrations/meta/ad-accountsAd accounts visible to the connected userBearer
PATCH/v1/stores/{storeId}/integrations/meta/ads/settingsChosen ad accounts, default account/window, extra storefront hostsBearer
GET/v1/stores/{storeId}/integrations/meta/ads/summaryAccount-level KPI headerBearer
GET/v1/stores/{storeId}/integrations/meta/ads/campaignsCampaign list + insightsBearer
GET/v1/stores/{storeId}/integrations/meta/ads/adsetsAd sets (filter by campaignId)Bearer
GET/v1/stores/{storeId}/integrations/meta/ads/adsAds decorated with destination URL, matched product, and that product's Feeef orders in-windowBearer
GET/v1/stores/{storeId}/integrations/meta/ads/ads/{adId}One ad in full (creative, URLs, KPIs)Bearer
GET/v1/stores/{storeId}/integrations/meta/ads/linksPersisted ad↔product mappingBearer
PATCH/v1/stores/{storeId}/integrations/meta/ads/links/{linkId}Manual product override (productId: null returns to auto-resolution)Bearer
POST/v1/stores/{storeId}/integrations/meta/ads/sync-linksFull account sweep — re-extract and re-resolve every adBearer

List queries share these params: adAccountId (required, act_ prefix optional), datePreset (e.g. last_7d) or since/until (YYYY-MM-DD; explicit range wins), effectiveStatus[] (ACTIVE, PAUSED, ARCHIVED, …), q (name search), after (Graph cursor), limit (1–100).

List campaigns with insights

curl -H "Authorization: Bearer $FEEEF_TOKEN" \
  "https://api.feeef.org/v1/stores/{storeId}/integrations/meta/ads/campaigns?adAccountId=act_1234567890&datePreset=last_7d&limit=25"

Feeef extracts each ad's destination URL, matches it to a store product (storefront hosts + extraStoreHosts from ads settings), and persists the mapping so ad KPIs can sit next to the product's real order counts. Resolution is lazy per page; POST …/ads/sync-links (body { "adAccountId": "act_…" }) forces a full sweep. Fix a wrong match with PATCH …/ads/links/{linkId} and body { "productId": "…" }.

Legacy pixel-connect routes

The pre-integration flow used for pixel discovery still exists and is public (raw access token passed by the client — prefer the store-scoped integration above for anything new):

MethodPathDescriptionAuth
GET/v1/social/facebook/marketing/redirectStart Facebook Marketing OAuth (popup/mobile)Public
ANY/v1/social/facebook/marketing/callbackShared OAuth callback (also completes store-scoped Meta connects)Public
GET/v1/social/facebook/marketing/pixels?accessToken=…List pixels visible to the tokenPublic
GET/v1/social/facebook/marketing/mobile-result?nonce=…One-time OAuth payload after deep-linkPublic
GET/v1/social/meta/oauth-result?nonce=…One-time outcome of a store-scoped Meta connectPublic

Pixel server events

Server events need the pixel integrations, not the Meta account: configure store.integrations.metaPixel.pixels[] (each entry: pixel id + CAPI access token key) and store.integrations.tiktokPixel (pixel id + accessToken) via the integrations config API. Events are built from an order and fanned out to every configured pixel; the response reports per-pixel success.

MethodPathDescriptionAuth
POST/v1/actions/sendEventToMetaPixelOne order → all Meta pixelsBearer
POST/v1/actions/sendEventsToMetaPixelMany orders — body adds orderIds[], optional pixelIds[] subsetBearer
POST/v1/actions/sendEventToTiktokPixelOne order → all TikTok pixelsBearer
POST/v1/actions/sendEventsToTiktokPixelMany orders (TikTok)Bearer
POST/v1/actions/sendMetaPixelTestEventTest CAPI credentials — body { "id", "key", "code"? }Bearer
POST/v1/actions/sendTiktokPixelTestEventTest Events API credentials — body { "id", "accessToken", "testCode"? }Bearer
POST/v1/actions/runPixelStatusRulesEvaluate (dry-run) or fire status-transition rules for an orderBearer

Accepted event values:

PlatformEvents
MetaPurchase, Lead, ViewContent, AddToCart, InitiateCheckout
TikTokPurchase, PlaceAnOrder, ViewContent, AddToWishlist, Search, AddPaymentInfo, AddToCart, InitiateCheckout, CompleteRegistration

Send a purchase event

curl -X POST "https://api.feeef.org/v1/actions/sendEventToMetaPixel" \
  -H "Authorization: Bearer $FEEEF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "event": "Purchase", "storeId": "{storeId}", "orderId": "{orderId}" }'

The fan-out response (HTTP 200 even on partial failure — inspect the counters):

{
  "event": "Purchase",
  "orderId": "…",
  "storeId": "…",
  "totalPixels": 2,
  "successfulPixels": 1,
  "failedPixels": 1,
  "successRate": 0.5,
  "hasAnySuccess": true,
  "results": [
    { "pixelId": "123", "pixelName": "Main", "success": true, "response": { "…": "…" } },
    { "pixelId": "456", "pixelName": "Backup", "success": false, "error": "…" }
  ],
  "summary": { "message": "Successfully sent event to 1 out of 2 pixels" }
}

Status rules

Pixel configs can carry status rules — "when the order transitions into status X, fire event Y". They run automatically on order updates; POST /v1/actions/runPixelStatusRules lets you audit or recover:

  • Default is dry-run: reports which rules would fire for the order's transition without calling any pixel API.
  • "dryRun": false actually sends.
  • "force": true fires rules whose target equals the order's current status (recovery / verification after missed sends).

The response lists outcomes per rule with wouldFire, sent, and skipReason.

Requirements checklist

  • Ads reads: store has a connected Meta account (capabilities.connected and canRead true) — tokens expire after ~60 days, watch needsReconnect.
  • Meta server events: integrations.metaPixel.pixels[] entries with valid CAPI tokens.
  • TikTok server events: integrations.tiktokPixel with a valid Events API token.
  • Writes to marketing configs follow the same RBAC + store.integrations scope rules as every integration.

On this page