.dev
REST API

Integrations

Per-store integration configs, the config envelope, and paid integration subscriptions with wallet billing.

Every store carries a JSON map of integration configs — store.integrations — one entry per integration key (pixels, delivery carriers, order export, webhooks, security, modules…). The endpoints on this page edit one integration at a time (instead of PATCHing the whole store) and manage per-integration subscriptions for paid integrations.

Request/response conventions (errors, pagination, batch) are covered in API conventions.

Integration keys

Keys addressable via /v1/stores/{id}/integrations/{key}:

GroupKeys
Pixels & analyticsmetaPixel, tiktokPixel (legacy alias tikTokPixel), googleAnalytics, googleTags, clarity
Marketingmeta — see Marketing
Delivery companiesConnected as carrier accounts on the Cloud parcel API. The older keys yalidine, ecotrack, zrexpress, procolis, noest, maystroDelivery, zimou, mdmExpress, ecomanager, codpilot, feeefDelivery, orderdz remain on this config map — see Delivery
Data & automationgoogleSheet, webhooks, connectors, ai, sms, telegram, dispatcher, customFields
Modulesinventory, finance, payment, security
Free-formmetadata

An unknown key returns 400 with Unknown integration key: {key}.

Config API — at a glance

All five routes require a bearer token with the store.integrations scope. Reads additionally need any member role on the store; writes need an editor/admin/owner role.

MethodPathDescriptionAuth
GET/v1/stores/{id}/integrationsAll configs + effective-active mapBearer + scope store.integrations
GET/v1/stores/{id}/integrations/{key}One config + effectiveActiveBearer + scope store.integrations
PATCH/v1/stores/{id}/integrations/{key}Deep-merge into one configBearer + scope store.integrations
PUT/v1/stores/{id}/integrations/{key}Replace one configBearer + scope store.integrations
DELETE/v1/stores/{id}/integrations/{key}Remove one configBearer + scope store.integrations

The config envelope

Single-key reads return the stored config plus effectiveActive — whether the integration is actually usable right now (active: true in the config and the store is entitled via plan, free catalog entry, or paid subscription):

{
  "storeId": "clx0a1b2c3",
  "key": "yalidine",
  "integration": { "id": "12345", "token": "…", "agent": "yalidine", "active": true },
  "effectiveActive": true
}

GET …/integrations returns the same idea for the whole map: { storeId, integrations, effectiveActive } where effectiveActive is a per-key boolean map. A lapsed subscription flips effectiveActive to false everywhere without touching the stored config.

Two fields are server-managed and redacted on every read:

  • meta.oauth2 (encrypted Meta access token) is replaced by an oauth2Connected boolean. Writes cannot set it either — only the OAuth callback can.
  • webhooks.webhooks[].secret is replaced by hasSecret. Secrets are revealed exactly once on create/rotate through the dedicated webhooks API.

Redacted fields survive a GET → PUT round-trip: omitting them means "keep the stored value".

Write semantics

  • PATCH deep-merges one level deep per key; nested arrays are replaced wholesale. The merged config is validated, not the patch in isolation — so PATCH {"active": true} on an already-configured integration succeeds, while enabling a half-configured one fails with 422 (e.g. clarity requires trackingCode when active: true).
  • PUT validates the body as the full config. Server-managed credentials are re-attached from the stored row.
  • DELETE removes the key. Deleting webhooks also drops the store's webhook delivery history.
  • Writes are applied under a row lock, then the store cache is busted.

Enabling a paid integration without entitlement fails with 402:

{
  "message": "Subscribe to \"Finance\" (1500 DZD/mo) before enabling it.",
  "integrationId": "finance",
  "requiresSubscription": true
}

Read and update a config

# Read one integration config
curl -H "Authorization: Bearer $FEEEF_TOKEN" \
  https://api.feeef.org/v1/stores/{storeId}/integrations/clarity

# Enable it (deep-merge)
curl -X PATCH "https://api.feeef.org/v1/stores/{storeId}/integrations/clarity" \
  -H "Authorization: Bearer $FEEEF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "trackingCode": "abcd1234", "active": true }'

customFields — order form fields

customFields defines extra fields on the store's order form (contract v2). The public copy is store.publicIntegrations.customFields (null when inactive). A product can replace the store list with product.integrationsData.customFieldsData = { useProductFields, fields }.

{
  "active": true,
  "fields": [
    { "id": "size", "label": "Size", "type": "select", "required": true, "order": 0,
      "options": [{ "value": "m", "label": "Medium" }, { "value": "custom", "label": "Custom" }] },
    { "id": "measures", "label": "Measurements", "type": "text", "maxLength": 120, "order": 1,
      "visibility": { "match": "all",
        "conditions": [{ "source": "field", "fieldId": "size", "operator": "equals", "value": "custom" }] } }
  ]
}
KeyRules
id^[A-Za-z0-9_-]{1,64}$, unique — the key in order.customFields
label1–80 chars
typetext textarea number phone email select checkbox image file date time datetime timerange daterange datetimerange
required, multiple, minCount, maxCountrequired only applies while visible; multiple (not checkbox) → array, ≤20 values (default 10)
placeholder, helpText≤120 / ≤300 chars
minLength, maxLength, regexPatternText types, ≤2000 chars; regex ≤200 chars, no nested quantifiers / backreferences / lookbehind
min, max, integernumber
optionsselect: 1–50 { value, label }, unique values
acceptfile: image pdf document spreadsheet archive text (default all)
visibility{ match: "all" | "any", conditions } — 1–10 conditions on an earlier field (equals notEquals in notIn filled empty gt lt checked unchecked) or on the cart products (source: "product", in / notIn)
defaultValue, order, active, metadataactive: false = never shown or accepted

At most 40 fields. Invalid lists are rejected with 422 on PATCH/PUT. Values, uploads and rendering rules are covered in Order form → Custom fields. To draft a list from a prompt, see custom-fields-draft.

Integration subscriptions

Paid integrations are billed per store, per integration, independent from the store plan. Charges debit the store owner's wallet once per 30-day period (idempotent per store + integration + month).

MethodPathDescriptionAuth
GET/v1/stores/{storeId}/integrations/subscription/catalogGlobal pricing catalog + billing rulesPublic
GET/v1/stores/{storeId}/integrations/subscription/statusStore's subscription entries + effectiveActive / includedInPlan per idBearer
POST/v1/stores/{storeId}/integrations/subscription/subscribeSubscribe (charges wallet when priced)Bearer
POST/v1/stores/{storeId}/integrations/subscription/cancelTurn off auto-renew; access kept until expiryBearer

Subscribe/cancel are restricted to the store owner or a member with the integrations.billing permission — read-only roles can never spend the owner's wallet.

Rules worth knowing:

  • Plans: ultra includes every integration; other plans include the items listing that plan in their catalog includedInPlans. Catalog items with price: 0 (or no catalog entry) are free.
  • Coupled pair: finance and inventory are subscribed and canceled together, and are project-scoped — a second store on the same project inherits the sibling's entitlement without a duplicate charge.
  • Delivery carriers need credentials first: subscribing to a carrier never creates an { "active": true } stub — configure credentials via the config API, then enable.
  • Dunning: a failed renewal moves the entry active → grace → past_due with exponential backoff retries, and to canceled after the max failed window. Lapsed entries make effectiveActive report false.
  • Insufficient balance returns 402 with a human-readable message.

Subscribe to a paid integration

curl -X POST "https://api.feeef.org/v1/stores/{storeId}/integrations/subscription/subscribe" \
  -H "Authorization: Bearer $FEEEF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "integrationId": "finance" }'

A successful subscribe reports how entitlement was granted:

{
  "message": "Subscribed successfully",
  "sharedProject": false,
  "includedInPlan": false,
  "free": false,
  "integrationIds": ["inventory", "finance"],
  "integrations": { "inventory": { "status": "active", "expiresAt": "…" }, "finance": { "…": "…" } },
  "effectiveActive": true
}

Cancel keeps access until the paid period ends — it only sets autoRenew: false.

Outbound webhooks

Order and product event webhooks (HMAC-signed pushes to your endpoint) live under the same base path (…/integrations/webhooks) but have a dedicated CRUD + test + delivery-history API. See Webhooks for events (orderCreated, productUpdated, …), payloads and signature verification.

On this page