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}:
| Group | Keys |
|---|---|
| Pixels & analytics | metaPixel, tiktokPixel (legacy alias tikTokPixel), googleAnalytics, googleTags, clarity |
| Marketing | meta — see Marketing |
| Delivery companies | Connected 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 & automation | googleSheet, webhooks, connectors, ai, sms, telegram, dispatcher, customFields |
| Modules | inventory, finance, payment, security |
| Free-form | metadata |
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.
| Method | Path | Description | Auth |
|---|---|---|---|
GET | /v1/stores/{id}/integrations | All configs + effective-active map | Bearer + scope store.integrations |
GET | /v1/stores/{id}/integrations/{key} | One config + effectiveActive | Bearer + scope store.integrations |
PATCH | /v1/stores/{id}/integrations/{key} | Deep-merge into one config | Bearer + scope store.integrations |
PUT | /v1/stores/{id}/integrations/{key} | Replace one config | Bearer + scope store.integrations |
DELETE | /v1/stores/{id}/integrations/{key} | Remove one config | Bearer + 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 anoauth2Connectedboolean. Writes cannot set it either — only the OAuth callback can.webhooks.webhooks[].secretis replaced byhasSecret. 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
PATCHdeep-merges one level deep per key; nested arrays are replaced wholesale. The merged config is validated, not the patch in isolation — soPATCH {"active": true}on an already-configured integration succeeds, while enabling a half-configured one fails with422(e.g.clarityrequirestrackingCodewhenactive: true).PUTvalidates the body as the full config. Server-managed credentials are re-attached from the stored row.DELETEremoves the key. Deletingwebhooksalso 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" }] } }
]
}| Key | Rules |
|---|---|
id | ^[A-Za-z0-9_-]{1,64}$, unique — the key in order.customFields |
label | 1–80 chars |
type | text textarea number phone email select checkbox image file date time datetime timerange daterange datetimerange |
required, multiple, minCount, maxCount | required only applies while visible; multiple (not checkbox) → array, ≤20 values (default 10) |
placeholder, helpText | ≤120 / ≤300 chars |
minLength, maxLength, regexPattern | Text types, ≤2000 chars; regex ≤200 chars, no nested quantifiers / backreferences / lookbehind |
min, max, integer | number |
options | select: 1–50 { value, label }, unique values |
accept | file: 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, metadata | active: 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).
| Method | Path | Description | Auth |
|---|---|---|---|
GET | /v1/stores/{storeId}/integrations/subscription/catalog | Global pricing catalog + billing rules | Public |
GET | /v1/stores/{storeId}/integrations/subscription/status | Store's subscription entries + effectiveActive / includedInPlan per id | Bearer |
POST | /v1/stores/{storeId}/integrations/subscription/subscribe | Subscribe (charges wallet when priced) | Bearer |
POST | /v1/stores/{storeId}/integrations/subscription/cancel | Turn off auto-renew; access kept until expiry | Bearer |
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:
ultraincludes every integration; other plans include the items listing that plan in their catalogincludedInPlans. Catalog items withprice: 0(or no catalog entry) are free. - Coupled pair:
financeandinventoryare 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_duewith exponential backoff retries, and tocanceledafter the max failed window. Lapsed entries makeeffectiveActivereportfalse. - Insufficient balance returns
402with 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.