# Superfiliate Partner API > REST API for Superfiliate partners: customers, campaigns, conversions, orders, custom reports, and webhooks. Authenticate every request with `Authorization: Bearer :`. - Base URL: `https://api.superfiliate.com` - Authentication: `Authorization: Bearer :` on every request - Full documentation as one markdown file: https://docs.superfiliate.com/llms-full.txt - Machine-readable OpenAPI 3.0 spec: https://api.superfiliate.com/api/v1/docs.json --- ## Agent Guides > Use agent-readable docs and dry-run requests to validate your Partners API integration. ### Copy-paste for your agent Paste this into Cursor, Claude Code, or another coding agent before it starts an integration: **Integration instruction** ```text You are integrating with the Superfiliate Partner API. Read https://docs.superfiliate.com/llms.txt first. Use https://docs.superfiliate.com/agents/dry-run-api for focused agent guidance and the dry-run API reference for endpoint details. Validate each request against https://api.superfiliate.com/api/dry-run/v1 before calling production. Send Authorization: Bearer superfiliate-dry-run-agent. The dry-run API never changes a store; fix any validation errors it returns before continuing. ``` ### Agent-readable docs Start with [llms.txt](https://docs.superfiliate.com/llms.txt) for a concise index, or [llms-full.txt](https://docs.superfiliate.com/llms-full.txt) for the complete Partners API documentation in one agent-readable document. ### Validate integrations Make requests to the [dry-run API](https://docs.superfiliate.com/agents/reference) under `/api/dry-run/v1`. It returns mocked results and realistic errors, so you can validate implemented logic without changing a production store. --- ## Getting Started > Create a key in Settings → Developers, authenticate, and make your first request. ### Base URL All requests are served over HTTPS from a single base URL: **Base URL** ```bash https://api.superfiliate.com ``` Every endpoint lives under the `/api/v1` prefix, and both requests and responses speak JSON. If you can make an HTTPS call, you can integrate with Superfiliate — there are no SDKs to install or frameworks to adopt. ### Make your first request 1. **Get your API credentials** Every integration authenticates with a `client_id` / `client_secret` pair, scoped per store. Create one in the Superfiliate app under **Settings → Developers**. Name it for the tool that will use it and copy the pair — the secret is shown once. If you're asked to pick scopes, grant only what the integration needs. The [API keys](https://docs.superfiliate.com/docs/api-keys) guide walks through generate and revoke. > Treat your secret like a password > > Store the `client_secret` in a secret manager and keep it out of client-side code and version control. Secrets are prefixed with `sfcs1_` on purpose: if one ever leaks, a code search finds it fast — revoke it and generate a replacement from the same Developers page. 2. **Authenticate** Join the two values with a colon and send them as a bearer token on every request: **Request header** ```http Authorization: Bearer {{client_id}}:{{client_secret}} ``` What each credential can touch is governed by scopes — see [Authentication](https://docs.superfiliate.com/docs/authentication) for the full list. 3. **Call the API** List the campaigns on your store: **List campaigns** ```bash curl "https://api.superfiliate.com/api/v1/campaigns" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` A healthy response comes back as plain JSON: **Response · 200 OK** ```json [ { "id": 123123, "name": "Wide Audience - Loyalty Program", "status": "active", "audience_type": "loyalty" }, { "id": 456456, "name": "Cat Owners Instagram - 2022", "status": "finished", "audience_type": "creators" } ] ``` 4. **Build from here** You are ready to build on top of the Partners API. Go to the [API reference](https://docs.superfiliate.com/reference) for endpoint details, request schemas, and examples. ### Rate limits Requests are rate limited with a leaky-bucket strategy: you get a burst of `50` requests, recovering at `2` requests per second. Go over and the API answers with `429 Too Many Requests` until the bucket drains. The [rate limiting guide](https://docs.superfiliate.com/docs/rate-limiting) covers how to stay comfortably under. ### Next steps --- ## Authentication > One bearer header, scoped credentials, and the endpoint access they unlock. ### The Authorization header Join your `client_id` and `client_secret` with a colon and send them as a standard bearer token on every request: **Request header** ```http Authorization: Bearer {{client_id}}:{{client_secret}} ``` In practice, with credentials kept in environment variables: **Authenticated request** ```bash curl "https://api.superfiliate.com/api/v1/customers?email=alice@example.com" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` > Where do credentials come from? > > Create them yourself in the Superfiliate app, under **Settings → Developers**. Each key is scoped to one store and to the access you grant. Rotation is the same screen: generate a new key, then revoke the old one. See [API keys](https://docs.superfiliate.com/docs/api-keys) for the walkthrough. ### Credential anatomy - `client_id` identifies your integration and starts with `sfci1_`. We store only a one-way digest of it, so it cannot be recovered from our side — keep your copy safe. - `client_secret` always starts with `sfcs1_`, which makes accidental leaks easy to find with code search. It also signs every webhook payload we send you — see [verifying webhook signatures](https://docs.superfiliate.com/docs/webhooks). ### Scopes Every credential carries a set of scopes, and every endpoint requires one. Write access implies read access for the same resource — a credential with `customers.write` can also call every `customers.read` endpoint. | Scope | Unlocks | | --- | --- | | `attribution.read` | [Get attribution installation](https://docs.superfiliate.com/reference/attribution/get-attribution-installation) | | `campaigns.read` | [List campaigns](https://docs.superfiliate.com/reference/campaigns/list-campaigns) | | `conversions.read` | [Search conversions](https://docs.superfiliate.com/reference/conversions/search-conversions) | | `conversions.write` | [Reject conversions](https://docs.superfiliate.com/reference/conversions/reject-conversion) | | `customers.read` | [Search customers](https://docs.superfiliate.com/reference/customers/search-customers) and [customer insights](https://docs.superfiliate.com/reference/customers/get-customer-insights) | | `customers.write` | [Create](https://docs.superfiliate.com/reference/customers/create-customer), [enroll](https://docs.superfiliate.com/reference/customers/enroll-customer), [update](https://docs.superfiliate.com/reference/customers/update-customer), [add balance](https://docs.superfiliate.com/reference/customers/add-balance), and [redact](https://docs.superfiliate.com/reference/customers/redact-customer) customers | | `discounts.read` | [List](https://docs.superfiliate.com/alpha/reference/discounts/list-discount-codes) and [get](https://docs.superfiliate.com/alpha/reference/discounts/get-discount-code) discount codes | | `gifting_campaigns.read` | [List gifting campaigns](https://docs.superfiliate.com/reference/gifting/list-gifting-campaigns) | | `gifting_campaigns.write` | [Enroll a customer in a gifting campaign](https://docs.superfiliate.com/reference/gifting/enroll-customer-in-gifting-campaign) | | `gifting_rewards.read` | [List gifting rewards](https://docs.superfiliate.com/alpha/reference/gifting/list-gifting-rewards) | | `gifting_rewards.write` | [Fulfill gifting rewards](https://docs.superfiliate.com/alpha/reference/gifting/fulfill-gifting-reward) | | `orders.write` | [Create or update orders](https://docs.superfiliate.com/alpha/reference/orders/create-order) | | `products.read` | [List products](https://docs.superfiliate.com/reference/products/list-products) | | `products.write` | [Create or update products](https://docs.superfiliate.com/reference/products/create-product) | | `reports.write` | [Create custom reports](https://docs.superfiliate.com/reference/reports/create-report) | | `reports.read` | [List reports](https://docs.superfiliate.com/reference/reports/list-reports) and [fetch report data](https://docs.superfiliate.com/reference/reports/fetch-report-data) | | `webhooks.read` | [List webhooks](https://docs.superfiliate.com/reference/webhooks/list-webhooks) | | `webhooks.write` | [Create](https://docs.superfiliate.com/reference/webhooks/create-webhook) and [remove](https://docs.superfiliate.com/reference/webhooks/delete-webhook) webhooks | Each endpoint's page in the [API reference](https://docs.superfiliate.com/reference) shows the scope it requires, right under the title. ### 401 vs 403 Two different problems, two different status codes: - `401 Unauthorized` — the credentials are missing or wrong. Check the header format and that the pair is active. - `403 Forbidden` — the credentials are valid, but they lack the scope this endpoint requires. Generate a new key with the missing scope — existing keys cannot be edited. **401 · wrong or missing credentials** ```json { "errors": ["Unauthorized"] } ``` **403 · missing scope** ```json { "errors": ["Forbidden"] } ``` --- ## API Keys > Create, copy, and revoke credentials in Settings → Developers. ### Create a key API keys live in the Superfiliate app, under **Settings → Developers**. Each key is a `client_id` / `client_secret` pair scoped to one store. 1. **Open Developers** In the Superfiliate app, go to Settings and open the Developers page. Open the **API keys** tab. 2. **Generate API key** Name the key after the tool that will use it — for example `Data warehouse`. If the dialog asks you to pick scopes, grant only what that integration needs. You cannot change scopes later: generate another key if the access list has to change. 3. **Copy the credentials** The confirmation dialog shows the client ID, the client secret, and the combined API key (`client_id:client_secret`). Send that combined value as a Bearer token — see [Authentication](https://docs.superfiliate.com/docs/authentication). > The secret is shown once > > Copy the `client_secret` into a secret manager before you close the dialog. Superfiliate stores only a digest of the client ID, so neither value can be recovered later. If you lose the pair, revoke it and generate a new key. ### What you get - `client_id` starts with `sfci1_`. It identifies the integration. - `client_secret` starts with `sfcs1_`. The prefix is there so a leaked secret is easy to find with code search. The same secret signs webhook payloads — see [verifying webhook signatures](https://docs.superfiliate.com/docs/webhooks). - The combined **API key** is the two values joined with a colon. That is the Bearer token. ### Scopes Every endpoint requires a scope, listed on its [reference page](https://docs.superfiliate.com/reference) and in the [Authentication](https://docs.superfiliate.com/docs/authentication) table. Write access implies read access for the same resource. Pick the smallest set the integration needs. A 403 means the key is valid but missing a scope — generate a new key with that scope included, then revoke the old one. ### Revoke a key Revoke from the same API keys list. The key stops working immediately, and the action cannot be undone. Confirm by typing `REVOKE`. Create a replacement key first if the integration still needs access. --- ## Attribution > Track conversions and attribute revenue to creators through their links and discount codes on a Custom OMS (non-Shopify) store. Attribution connects a customer’s journey to the creator who referred them, so you can measure conversions and credit creators for the revenue they generate. For a Custom OMS (non-Shopify) store, your storefront records the customer journey and your order management system submits confirmed paid orders from your backend. Your commerce platform continues to manage checkout, discounts, payments, and fulfillment. The required flow is: creator link → your storefront → checkout → browser checkout event plus authenticated order API request → attribution in Superfiliate. > Shopify stores > > For standard Shopify storefronts, tracking is managed by the Shopify app. Settings → Developers shows the attribution script as managed by Shopify. The steps below cover Custom OMS stores. > > [![Developers → Attribution script on Shopify: the Superfiliate app embed is Active on the published theme.](https://docs.superfiliate.com/images/guides/shopify-managed-attribution.png)](https://docs.superfiliate.com/images/guides/shopify-managed-attribution.png) > > Active confirms the app embed is enabled on the published Shopify theme. Open the image to view it at full size. ### Before you start with a Custom OMS > Custom commerce access > > Order ingestion is currently Alpha, available to approved integrations, and may change. Confirm access and an active custom commerce integration with Superfiliate before implementation. The order API reference lives in the Alpha section; requests still use the normal API base URL. - Identify who can edit DNS, storefront code, checkout confirmation code, and the backend that receives confirmed payment/order notifications. - Confirm the storefront and checkout hostnames, store currency, program rewards, attribution window, and test environment with Superfiliate. Custom order conversion processing currently requires the order currency to match the configured store currency. - Create server-side credentials in [Settings → Developers](https://docs.superfiliate.com/docs/api-keys). The examples read them from `SF_CLIENT_ID` and `SF_CLIENT_SECRET` environment variables. | Scope | When you need it | | --- | --- | | orders.write | Required: submit confirmed orders. | | attribution.read | Fetch the script tag through the API; copying it from the app does not need this scope. | | conversions.read | Check attribution through the API. | Keep API credentials on your backend. The public storefront script uses your shop identifier and does not need an API secret. > Migrating an existing creator program? > > Follow [Import creators and codes](https://docs.superfiliate.com/docs/import-creators) to bring your list over with the CSV migration tool or the API. Confirm the creator, program, and code mapping before testing attribution. ### 1\. Connect your creator-link domain Use a branded subdomain such as `partners.yourstore.com`, ideally on the same root domain as your store. A link such as `https://partners.yourstore.com/JOHN` identifies the creator before sending the customer to your store or creator landing page. Sharing the root domain also helps preserve tracking cookies. In Settings → Custom Domain, add the CNAME and ownership TXT records shown in the app, verify them, then complete Connect domain. Follow [Custom Domain Set Up](https://help.superfiliate.com/articles/5309826933-custom-domain-set-up) for the current DNS instructions. For help identifying your domain provider or checking CNAME and other DNS records, use the [Domain Provider & Verification Guide](https://help.superfiliate.com/articles/5361979859-how-to-check-your-domain-provider-and-verify-custom-domain-integration). The CNAME connects your creator-link hostname. A separate redirect destination setting chooses where customers go when Cobranded Landing Pages are disabled. For direct-to-store links, follow [Choose where your creator links send shoppers](https://help.superfiliate.com/articles/8555205862-choose-where-your-creator-links-send-shoppers). Keep tracking query parameters through every redirect. Connecting the domain does not install tracking or apply discounts on your checkout. ### 2\. Install the script and track the customer journey In Settings → Developers → Attribution script, copy your store’s script tag. You can also fetch it with [GET /api/v1/attribution](https://docs.superfiliate.com/reference/attribution/get-attribution-installation). Install it on every storefront page, including product pages, cart, checkout, and the order confirmation/thank-you page. To fetch the tag from your backend, use credentials with the `attribution.read` scope: **Fetch your store’s attribution script** ```bash curl "https://api.superfiliate.com/api/v1/attribution" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` **Example response · 200 OK** ```json { "scriptTag": "", "version": "1" } ``` Use the returned `scriptTag` for your store. Compatible script updates keep the same URL and installation `version`. Place the copied tag in the document head. Replace the example shop identifier with the tag copied from Developers: **Shared page head** ```html ``` > Loading the script is only the first step > > The script initializes `window.$superfiliateStorefront.sdk`. Your integration must call the handlers below. A script load alone does not record the required store-entry or checkout events. Add the tracking calls to your existing storefront code, in a file or module you control. Run the JavaScript examples inside an async callback or module. The helper below waits until the SDK is available, then stores the SDK object in `superfiliateSdk` so you can call its methods directly. It returns immediately if the script has already loaded, or throws an error after 10 seconds if it remains unavailable: **Track store entry on each page load** ```javascript async function waitForSuperfiliateSdk(timeoutMs = 10000) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { const sdk = window.$superfiliateStorefront?.sdk; if (sdk) return sdk; await new Promise((resolve) => setTimeout(resolve, 50)); } throw new Error("Superfiliate SDK did not load. Check the storefront script."); } const superfiliateSdk = await waitForSuperfiliateSdk(); await superfiliateSdk.handlePageView(); ``` Call `handlePageView()` while the original landing URL still contains its tracking parameters. The SDK reads Superfiliate’s UTM parameters, including `utm_source` and the encoded attribution data in `utm_content`. It also recognizes `attributes[sf-origin]` and `attributes[sf-external-session-uuid]` when present. Keep the parameters supplied by the creator link; you do not need to parse them yourself. On an attributed visit, the SDK saves a cookie named `sf_microsite_session`. Its value is an anonymous UUID identifying the customer’s attribution session. It connects the customer’s visit to their later checkout; it does not contain their email or your OMS customer ID. The SDK sends `external_access` and `main_store_access` when applicable. No customer email or order data is needed for this call, and repeated navigation in the same session does not emit another store-entry event. On a single-page application, also call the handler after route changes, before removing any new attribution parameters: **Inside your router’s navigation handler** ```javascript await superfiliateSdk.handlePageView(); ``` After your OMS confirms the order is paid, call the checkout handler on the confirmation/thank-you page with your actual order data. The storefront script and page-view initialization above must have completed first. If this is a new page load, use the same readiness helper to initialize `superfiliateSdk` there before calling the handler. A click on Pay alone does not confirm payment. **Track a completed checkout** ```javascript await superfiliateSdk.handleCheckoutCompleted({ id: "order-123", totalValue: "100.00", currency: "USD", discountCodes: ["JOHN"], customer: { id: "customer-456", email: "buyer@example.com", }, }); ``` | Browser checkout field | Contract | | --- | --- | | id | Required. Your stable commerce order ID, exactly matching external\_id in the backend request below. Use the same string, not a separate checkout/payment ID. | | totalValue, currency | Optional browser snapshot of the order total and currency. | | discountCodes | Optional array of applied codes. Use \[\] when no code was applied. | | customer.id, customer.email | Optional commerce customer ID and email; these are the buyer’s details, not the creator’s details. | The handler sends `order_completed`, attaching the session UUID, shop ID, and custom-commerce marker automatically. It does not send a referral checkout event when there is no attribution session. Use these handlers instead of constructing the raw tracking payload with `sendEvent()`. > Keep tracking connected through checkout > > `handleCheckoutCompleted()` reads `sf_microsite_session` to identify the customer’s attribution session. The same cookie value must be available on the page where you call it. A cookie scoped to `yourstore.com` can be available on `www.yourstore.com` and `checkout.yourstore.com`, but cannot be read on a different root domain such as `checkout.paymentprovider.com`. > > If checkout uses a different root domain, agree on a supported confirmation page or session handoff with Superfiliate before launch. Installing the script on both domains does not share the cookie. Connect tracking to your consent flow and test with consent granted. ### 3\. Send confirmed orders from your backend Send the order from your backend once your OMS has confirmed payment. Superfiliate matches this trusted order record with the browser event to connect the customer’s journey to a paid conversion. The authenticated request provides the order status and amounts; the browser event on its own is not enough to record the order for attribution. Trigger this request from your backend after verifying your commerce platform’s order/payment notification. Build the payload from your stored order, and send it for every paid order, including orders without a creator code. Superfiliate does not automatically handle cancellations or refunds from your OMS, so do not submit an order as paid before payment is confirmed. Agree on how later cancellations and refunds will be reconciled before launch. Use your backend’s order data for payment status and totals. **Backend only · POST /api/v1/orders** ```bash curl -X POST "https://api.superfiliate.com/api/v1/orders" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "external_id": "order-123", "status": "paid", "currency": "USD", "customer_email": "buyer@example.com", "subtotal_value": "90.00", "total_value": "100.00", "discount_code": "JOHN", "external_created_at": "2026-10-22T15:30:00Z", "line_items": [ { "external_id": "line-1", "external_product_id": "product-1", "external_variant_id": "variant-1", "name": "Everyday T-shirt", "sku": "TSHIRT-BLACK-M", "quantity": 2, "gross_unit_price": "50.00", "discount_total": "10.00", "subtotal_price": "90.00" } ] }' ``` | API field | What to send | | --- | --- | | external\_id | Required stable order ID. Here: "order-123", matching the browser id exactly. | | status | Required: "paid" for a confirmed paid order. "pending" is also accepted, but does not trigger conversion creation until updated to paid. | | currency, customer\_email | Required: ISO currency code and the buyer’s email. The API finds or creates the buyer by normalized email. | | subtotal\_value, total\_value | Required non-negative decimal strings, with at most two decimal places. Subtotal is after discounts, before shipping/tax; total includes shipping/tax. This example has 100.00 of merchandise, 10.00 discount, and 10.00 shipping/tax. | | external\_created\_at | Required authoritative order timestamp in RFC 3339 with a timezone. Keep the original time on retries. | | line\_items | Required non-empty array. Each item needs external\_id, name, positive integer quantity, and subtotal\_price (line total after discounts). Product/variant IDs, SKU, gross\_unit\_price, and discount\_total are optional; send them when available. | | discount\_code | Optional single applied creator code. Omit it for the no-code attribution test. The API accepts one code, while the browser snapshot accepts an array. | A `200` response returns the saved order with a Superfiliate `id` and your `external_id`. Save both for troubleshooting. The response confirms persistence; conversion processing happens asynchronously and still depends on attribution and program eligibility. See the [Alpha order reference](https://docs.superfiliate.com/alpha/reference/orders/create-order). - Retry network errors, 429s, and server errors through a durable backend queue, with backoff. Reuse the same external ID and complete order payload; repeated requests update that order rather than insert a new one. Do not generate a new ID on retry. - Fix credentials for 401, scopes for 403, and the response’s validation errors for 422 before retrying. See [Errors](https://docs.superfiliate.com/docs/errors) and [Rate Limiting](https://docs.superfiliate.com/docs/rate-limiting). - A mapped creator discount code can attribute the order directly. For no-code link attribution, Superfiliate matches the browser checkout event by order ID and uses its session’s related access event. Browser snapshot amounts do not replace the authenticated order data. ### 4\. Test attribution end to end Use an agreed test store and test creator, with program rules that allow the test purchase. [Dry-run requests](https://docs.superfiliate.com/agents/dry-run-api) validate API contracts with synthetic data; they do not prove that your DNS, browser session, checkout, and real order pipeline are connected. 1. **Follow the real creator link** Start in a fresh browser session and open the creator’s returned referral URL. Confirm HTTPS, the correct destination, and preserved tracking parameters. Accept tracking consent for the positive test. 2. **Check the attribution cookie and store-entry event** After the attributed landing page calls `await superfiliateSdk.handlePageView()`, open Chrome DevTools → Application → Storage → Cookies and select your store domain. Confirm `sf_microsite_session` exists, note its UUID value, and check that its Domain covers your storefront and checkout/confirmation subdomains. In Chrome’s Network tab, filter for `/api/events`. Browser events are sent by POST to your configured creator-link domain, for example `https://partners.yourstore.com/api/events`. The exact hostname is the `shop.micrositeDomain` value in your storefront configuration. Check for a successful 2xx response and inspect the request payload: the `event_type` should be `external_access` or `main_store_access`, and its `uuid` should match the cookie. Check the creator `code` when present. Developers → Attribution script should become active after an event arrives. This status confirms event activity; continue through a paid test order to verify attribution. 3. **Complete a purchase without a discount code** Navigate through product, cart, checkout, and confirmation pages. Confirm your OMS marks the order paid and that the original `sf_microsite_session` value is still readable on the page calling `handleCheckoutCompleted()`. Enable Preserve log in Chrome’s Network tab to follow redirects. At the same `/api/events` URL, inspect the POST with `event_type: order_completed`. Confirm a successful response, the same session `uuid`,`commerce_platform: custom`, and the correct `order_external_id`. A no-code purchase isolates link/session attribution. 4. **Check the backend submission and conversion** Confirm your backend submits the same order ID with paid status and receives 200. Check Superfiliate’s conversion reporting for the intended creator/program and the API-submitted amounts after processing. You can also use [Search conversions](https://docs.superfiliate.com/reference/conversions/search-conversions) with `conversions.read`. If missing, collect the referral URL, order IDs, event UUID, request/response, and timestamps for Superfiliate support; exclude credentials. **Check JOHN’s conversions, including pending processing** ```bash curl "https://api.superfiliate.com/api/v1/conversions?code=JOHN&include_pending=true&page=1&items=25" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` 5. **Check code attribution and retries separately** Place another order with JOHN applied and confirm the creator mapping. Resubmit its identical backend payload and refresh the thank-you page: verify one order and one conversion remain. In another fresh browser session, test an organic visit with no code and confirm it is not credited to JOHN. Repeat the affected scenarios for mobile, returning visits, SPA navigation, your actual checkout hostname, and delayed/out-of-order browser and server delivery. With declined consent or blocked scripts, verify the authenticated order still arrives and that missing browser tracking does not create false link attribution. ### Launch checklist - Domain connected, real creator link verified, and redirect parameters preserved. - Creators mapped to the intended program and existing codes checked in both systems. - Store-entry and checkout handlers exercised across the real shopping journey. - Authenticated paid orders delivered reliably, with matching browser/server order IDs. - No-code and code-based test conversions checked for creator, amounts, and duplicates. - Owners assigned for failed order deliveries and attribution issues. Before choosing a launch date, agree on migration volume, historical data scope, refund/cancellation handling, supported currencies, and any subscription-renewal requirements with Superfiliate. The order contract shown here accepts pending/paid; it does not define a refund or cancellation workflow. Estimate implementation time after checking DNS access, checkout extensibility, and backend order notifications. ### Optional next steps - [Sync discount codes](https://docs.superfiliate.com/docs/sync-discount-codes) to create and update discounts in your own commerce platform. - [Fulfill gifts with your OMS](https://docs.superfiliate.com/docs/fulfill-gifts) to automate gift orders and report placement and shipment. --- ## Import creators and codes > Migrate existing creators and referral codes with the CSV import tool or the API. Bring your existing creators into Superfiliate with the email, program, and referral code they already use. For a one-time migration, use the CSV import tool in the app. Use the API when you need a scripted import or ongoing enrollment from another system. Both options create the creator and code mapping used for attribution. For a Custom OMS (non-Shopify) store, complete the storefront tracking and paid-order integration in [Attribution](https://docs.superfiliate.com/docs/attribution) as well. ### Prepare your creator data Choose the destination program and check a small sample before importing the full list. Use each creator’s email, rather than the email of a shopper they referred. For example: | Field | Example | What to check | | --- | --- | --- | | Email | john@example.com | Required. Use the email the creator will use to access their portal. | | Name | John | Optional profile information. | | Code | JOHN | Include the existing public code to preserve it. Resolve duplicate codes assigned to different creators before importing. | | Program | Ambassadors | Choose the program in the CSV importer, or pass its campaign\_id through the API. | Keep existing discounts active in your commerce platform. For non-Shopify stores, importing a code establishes its Superfiliate mapping; your own commerce platform still applies and manages the discount at checkout. Agree on a cutover date and how previously shared links will continue to work. Plan historical orders, commissions, and balances separately from this creator/code import. ### Option 1: use the CSV migration tool Use this option to upload an existing creator list without building an API integration. The importer lets you choose a program, map your columns, and review the import results. 1. **Open Import creators** In Creators, open More actions → Import creators. Select the target program and give the imported list a name. 2. **Download and fill in the template** Select Download template in the importer. Keep its helper header. Fill in email and the existing code for each creator, plus any profile information you want to bring over. Email is the only mandatory field; a supplied code is needed to preserve that code. 3. **Choose how existing codes are handled** To associate an existing merchant-managed code with its creator, set `code_activation` to `link`. Review this choice for every row. The CSV importer defaults a blank activation value to `override`. 4. **Upload, map, and review** Upload your CSV and match its columns to the creator properties, including Email, Code, and Code activation. Select Ignore this field for columns you do not want to import. Review the selected program, mapped values, and code replacement notice, then select Import. 5. **Check the results** Follow the import’s details page, or open Creators → Imported lists. Check the processed counts and Import errors. Correct failed rows before uploading them again, then verify a sample of creator profiles and codes. > Review existing creators before submitting > > CSV imports can update existing profiles, move creators to the selected program, and replace their current code. A replaced code may stop working or being tracked. Confirm the intended changes before the full import, especially for creators who already share active links. For the full walkthrough, see [How to Import Existing Contacts To Your Superfiliate Account](https://help.superfiliate.com/articles/5951132039-how-to-import-superfiliates-to-your-program). Its Shopify code-activation options describe how Shopify discounts are created or replaced; they do not create discounts in an external commerce platform. ### Option 2: import through the API Create server-side credentials with `customers.write` and `campaigns.read`. Write access also permits customer reads. Keep the client ID and secret on your backend; see [API Keys](https://docs.superfiliate.com/docs/api-keys). 1. **Find the target program** List programs and save the desired program’s ID. These are affiliate programs, separate from gifting campaign IDs. **List programs** ```bash curl "https://api.superfiliate.com/api/v1/campaigns" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` 2. **Find or create the creator** Submit the creator’s email and optional profile fields. If that email already exists, the endpoint returns the existing customer and updates supplied non-blank profile fields. Omit fields you do not intend to update, or search by email first and reuse the existing ID. Save the returned Superfiliate customer `id`; it is separate from your OMS customer ID. **Create or find John** ```bash curl -X POST "https://api.superfiliate.com/api/v1/customers" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{"email":"john@example.com","first_name":"John"}' ``` 3. **Enroll with the existing code** If the returned customer ID is `789012` and the target program ID is `345678`, associate JOHN with that creator: **Preserve the existing merchant-managed code** ```bash curl -X POST "https://api.superfiliate.com/api/v1/customers/789012/enroll" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "campaign_id": 345678, "code": "JOHN", "code_activation": "link" }' ``` For an existing code that your store manages, use `code_activation: link`. To generate new codes instead, follow the options in [Enroll a customer](https://docs.superfiliate.com/reference/customers/enroll-customer). > Existing enrollments are returned unchanged > > If the customer already belongs to a program, the enrollment endpoint returns that existing enrollment. It does not move the creator or replace their code. Check the returned program and code, and resolve mismatches before continuing. The CSV importer has different update behavior, described above. Repeat the customer and enrollment requests for each record. Save the source record ID, returned Superfiliate customer ID, program, and code so you can resume failed rows without reimporting the whole list. Observe the [rate limits](https://docs.superfiliate.com/docs/rate-limiting) and retry transient failures with backoff. Fix code conflicts or validation errors before retrying affected records. Both API requests write real records; validate a small batch in your agreed test store first. ### Verify the migration - Compare imported/successful counts with your source list and resolve every failed record. - Check each sampled creator’s email, program, and code. Through the API, verify `campaign_id`, `code`, and `microsite_url` in the customer response. - Open the returned referral URL and confirm its destination. Use this URL when sharing links rather than constructing it yourself. - Test a purchase through the creator link without a code, then a separate purchase with the existing code. For a Custom OMS store, use the cookie, browser event, and paid-order checks in [Attribution](https://docs.superfiliate.com/docs/attribution) and confirm the intended creator receives attribution. **Look up the imported creator** ```bash curl "https://api.superfiliate.com/api/v1/customers?email=john@example.com" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` --- ## Sync discount codes > Use code notifications and the API to create and update discounts in your commerce platform. Automatically create a checkout discount when a creator receives a referral code in Superfiliate, then keep it in sync as the code changes. For a Custom OMS (non-Shopify) store, Superfiliate exposes the code and offer configuration; your backend creates the usable discount in your commerce platform. Existing discounts can stay active while you connect this flow. ### Before you start Create server-side credentials with `discounts.read` and `webhooks.write`. Confirm that your custom commerce integration is active, and keep credentials on your backend. See [API Keys](https://docs.superfiliate.com/docs/api-keys). > Alpha discount lookup > > Discount lookup is currently Alpha, available to approved integrations, and may change. Confirm access with Superfiliate before implementation. ### Subscribe to code changes Register one subscription for each topic before enrolling creators. The example uses a different URL for each topic so your receiver knows which action to take; deliveries contain the code object directly, without a topic wrapper. Then reconcile existing codes through the [Alpha discount lookup](https://docs.superfiliate.com/alpha/reference/discounts/list-discount-codes): **Subscribe to code creation, updates, and deletion** ```bash curl -X POST "https://api.superfiliate.com/api/v1/webhooks" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourstore.com/webhooks/superfiliate/discount_code_created", "topic": "discount_code_created" }' curl -X POST "https://api.superfiliate.com/api/v1/webhooks" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourstore.com/webhooks/superfiliate/discount_code_updated", "topic": "discount_code_updated" }' curl -X POST "https://api.superfiliate.com/api/v1/webhooks" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourstore.com/webhooks/superfiliate/discount_code_deleted", "topic": "discount_code_deleted" }' ``` Verify each delivery’s raw-body signature with the secret of the API key that created the subscription, using the [webhook guide](https://docs.superfiliate.com/docs/webhooks#verification). Save the work to a durable queue before returning a `2xx` response. Deliveries can repeat or arrive out of order; reconcile against the current lookup before applying a delayed creation or update. For a deletion, retain the delivered code snapshot and your local mapping so you can disable the corresponding discount. ### Apply the offer in your commerce platform After affiliate enrollment returns `JOHN` for program `345678`, look up the offer. The response is an array, even when filtering by one code: **Look up JOHN’s configuration** ```bash curl "https://api.superfiliate.com/api/v1/discount_codes?campaign_id=345678&code=JOHN" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` **Example lookup response · relevant fields** ```json [ { "id": 456789, "code": "JOHN", "campaign_id": 345678, "lead_reward_unit": "percentage", "lead_reward_value": "10.0", "lead_discount_min_value": "50.0", "lead_discount_new_customers_only": true, "lead_apply_once_per_customer": true } ] ``` This example gives eligible new customers 10% off orders that meet the 50.00 minimum, once per customer. Create those rules in your discount engine before sharing the code. These are illustrative settings; use the values returned for your program. | Fields | How to use them | | --- | --- | | id, code, campaign\_id | Save the Superfiliate code ID, code text, and program ID alongside your commerce platform’s discount ID. Update that mapping when a code changes. | | lead\_reward\_unit, lead\_reward\_value | The shopper’s offer. percentage with 10.0 means 10%; fixed\_amount means a fixed discount amount in the store’s currency. | | lead\_discount\_min\_value, lead\_discount\_new\_customers\_only, lead\_apply\_once\_per\_customer | Enforce the minimum spend and customer eligibility rules in your checkout. | | reward\_unit, reward\_value | The creator’s reward configuration. Do not use these values as the shopper’s checkout discount. | - Upsert or disable local codes idempotently so retries do not create duplicates. If the code text changes, retire the old checkout code as part of the update. Reconcile periodically with the lookup API to catch missed notifications. - Confirm your discount engine can enforce the configured offer and eligibility rules. Resolve unsupported rules before activating a code. - Test the code in your checkout, then include the code actually applied in the backend paid-order request as `discount_code`. The browser checkout event uses `discountCodes`, an array. Follow the examples in [Attribution](https://docs.superfiliate.com/docs/attribution). A redirect does not apply the discount for you. Affiliate programs (`/campaigns`) and gifting campaigns (`/gifting_campaigns`) use different IDs. If a creator should receive both a referral code and a gift, configure both flows; gifting enrollment alone does not establish the referral-code setup. See [Add creators to gifting campaigns](https://docs.superfiliate.com/docs/gifting). ### Test discount synchronization - Enroll a test creator and confirm their code is created in your commerce platform with the configured shopper offer and eligibility rules. - Replay a webhook and confirm it does not create duplicate discounts. Change the code and confirm the old code stops working. Delete a code and confirm its local discount is disabled. Check delayed deliveries do not restore a retired code. - Check eligible and ineligible customers, the minimum spend, and a second use by the same customer. Confirm checkout enforces your actual offer configuration. - Complete a test purchase with the code and confirm the authenticated order includes the code actually applied. Follow the end-to-end tests in [Attribution](https://docs.superfiliate.com/docs/attribution). --- ## Add creators to gifting campaigns > Find or create customers and add them to a gifting or seeding campaign. Add creators to an existing gifting or seeding campaign using their Superfiliate customer IDs. This works for Shopify and merchant-managed campaigns, whether or not the creator belongs to an affiliate program. ### Create API credentials In Settings → Developers, create a key with `customers.write` and `gifting_campaigns.write`. Each write scope also permits reads for that resource. These permissions must be explicitly granted to the key. Keep credentials on your server; see [API Keys](https://docs.superfiliate.com/docs/api-keys). ### Find or create the customer Call [GET /api/v1/customers?email=john@example.com](https://docs.superfiliate.com/reference/customers/search-customers). If the array is empty, call [POST /api/v1/customers](https://docs.superfiliate.com/reference/customers/create-customer) with an email and optional first name, last name, phone and social handles. Save the returned `id`; this is a Superfiliate ID, separate from a Shopify or OMS customer ID. Use the creator’s email, not the email of a shopper they referred. **Find John by email** ```bash curl "https://api.superfiliate.com/api/v1/customers?email=john@example.com" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` **Create John if the search returns an empty array** ```bash curl -X POST "https://api.superfiliate.com/api/v1/customers" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{"email":"john@example.com","first_name":"John"}' ``` You can also call POST directly: an existing email returns that customer and updates any supplied non-blank profile fields. Omit fields you do not intend to update. The remaining examples assume the returned customer ID is `789012`; replace it with your actual ID. ### Choose the gifting campaign Call [GET /api/v1/gifting\_campaigns?active=true](https://docs.superfiliate.com/reference/gifting/list-gifting-campaigns) and save the desired campaign ID. These IDs are separate from affiliate program IDs returned by `/api/v1/campaigns`. **Find an active campaign for your Custom OMS** ```bash curl "https://api.superfiliate.com/api/v1/gifting_campaigns?active=true&fulfillment_mode=merchant_managed&page=1&items=25" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` For Shopify campaigns, use `fulfillment_mode=shopify`. Request successive pages until an empty array is returned. The next example assumes you chose gifting campaign `345678`. ### Enroll the customer **Enroll a creator in campaign 345678** ```bash curl -X POST "https://api.superfiliate.com/api/v1/gifting_campaigns/345678/enroll" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{"customer_id":789012}' ``` **200 response** ```json { "id": 123456, "customer_id": 789012, "gifting_campaign_id": 345678, "status": "unlocked" } ``` Save the returned `id` as the gift/reward ID. Here, `123456` identifies the gift, not the creator or campaign. Use it later when reporting OMS fulfillment. Repeated calls return the latest existing gift without issuing another, including when that gift is claimed or expired. An archived or expired campaign returns 422; a missing customer or campaign returns 404. Recurring campaigns continue to refresh gifts on their configured schedule. > Adding a creator and sending a gift > > Manual gifts are unlocked for claiming. Automatic Shopify gifts enter ready\_to\_send when an address exists, or missing\_address otherwise. This endpoint does not immediately place a Shopify order; use the normal Send gift flow. Campaign automations run for new additions. See the [endpoint reference](https://docs.superfiliate.com/reference/gifting/enroll-customer-in-gifting-campaign) for the full contract. To test without writes, use the matching [dry-run endpoint](https://docs.superfiliate.com/agents/reference/gifting/dry-run-enroll-customer-in-gifting-campaign) with campaign `900901`, customer `900002` and bearer token `superfiliate-dry-run-agent`. For merchant-managed campaigns, continue with [Fulfill gifts with your OMS](https://docs.superfiliate.com/docs/fulfill-gifts) to automate order placement and report shipment. To give the same creator a referral code, enroll them separately in an affiliate program using the same customer ID. Follow [Import through the API](https://docs.superfiliate.com/docs/import-creators#api) for enrollment and [Sync discount codes](https://docs.superfiliate.com/docs/sync-discount-codes) to create the discount in your Custom OMS checkout. ### Test gifting enrollment - Enroll a test creator in your intended campaign and confirm the returned customer and campaign IDs match. Check that their gift appears in the app. - Repeat the enrollment request and confirm the same gift ID is returned without issuing another gift. - For a manual campaign, have the creator claim the gift and submit their details. For merchant-managed campaigns, continue with the fulfillment test before enabling automatic order creation. --- ## Import your product catalog > Import products and variants from your Custom OMS through the API, then choose them for gifting campaigns. Import your Custom OMS product catalog into Superfiliate so you can choose products and variants for merchant-managed gifting campaigns. Your OMS remains responsible for inventory, order creation, and shipping. The API creates or updates Superfiliate’s copy of each product and its variants. ### 1\. Create API credentials In Settings → Developers, create server-side credentials with `products.write`, which also permits product reads. Keep the client ID and secret on your backend. The examples use `SF_CLIENT_ID` and `SF_CLIENT_SECRET` environment variables; see [API Keys](https://docs.superfiliate.com/docs/api-keys). ### 2\. Map your products and variants | Field | What to send | | --- | --- | | source, external\_id | Required strings. Use a stable catalog source such as custom\_oms and your existing OMS product ID. Together they identify the product within your store. | | title, url | Required product name and HTTP(S) product page URL. | | image\_url | Required public HTTP(S) image URL that Superfiliate can fetch. Replace the example URL with a real accessible image. | | variants\[\].external\_id, variants\[\].title | Required for each supplied variant. Use its stable OMS variant ID and a readable name such as Black / Medium. | | variants\[\]: sku, price, options | Optional SKU, decimal price in your store’s configured currency, and name/value options. Send prices as strings, such as 29.99. | Include the variants creators should be able to choose. Keep external IDs unchanged on later imports; they connect selected gift items back to your OMS catalog. Use the returned Superfiliate product `id` for API lookups, rather than substituting your external ID in the URL. ### 3\. Send one product per request **Create or update a product with one variant** ```bash curl -X POST "https://api.superfiliate.com/api/v1/products" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "source": "custom_oms", "external_id": "everyday-t-shirt", "title": "Everyday T-shirt", "url": "https://yourstore.com/products/everyday-t-shirt", "image_url": "https://yourstore.com/images/everyday-t-shirt.jpg", "variants": [ { "external_id": "everyday-t-shirt-black-medium", "title": "Black / Medium", "sku": "TSHIRT-BLK-M", "price": "29.99", "options": [ { "name": "Color", "value": "Black" }, { "name": "Size", "value": "Medium" } ] } ] }' ``` A successful `200` response returns the product object and its variants, including their Superfiliate IDs. Save those IDs alongside your external IDs, then repeat the request for each product in your list. See [Upsert product](https://docs.superfiliate.com/reference/products/create-product) for the full field contract. - Reusing the same `source` and `external_id` updates the product. Each included variant is updated by its own external ID within that product. - Each request supports up to 100 variants. For larger products, split variants across requests with the same product fields and IDs. Omitted variants remain unchanged; sending a shorter list does not delete them. - To retire a product or variant, resend its required fields with `active: false`. Omit optional fields you want to preserve; supplied `options` replace that variant’s option list. - Save progress per product. Retry network errors, 429s, and server errors with the same IDs and backoff; fix validation errors before retrying. Follow the [rate limits](https://docs.superfiliate.com/docs/rate-limiting). ### 4\. Verify the catalog and choose gift products Fetch a product using its returned Superfiliate ID. This example assumes `123456`; replace it with your actual ID: **Check the imported product and variants** ```bash curl "https://api.superfiliate.com/api/v1/products/123456" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` Compare the external IDs, variant options, prices, and image with your source data. List the catalog to reconcile your import, requesting successive pages until an empty array is returned: **List imported products** ```bash curl "https://api.superfiliate.com/api/v1/products?page=1&items=25" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` Importing products does not automatically add them to a gifting campaign. Configure the imported variants in your merchant-managed campaign, then have a test creator submit their selections. Confirm the resulting reward’s `selected_products` contains the expected product and variant external IDs. Continue with [Fulfill gifts with your OMS](https://docs.superfiliate.com/docs/fulfill-gifts) to create the gift order from those selections. --- ## Fulfill gifts with your OMS > Create merchant-managed gift orders in your system and report order placement and shipment to Superfiliate. Automate merchant-managed gifting through your own order management system (OMS) or fulfillment provider. Your backend places the gift order and reports order placement and shipment back to Superfiliate. The flow is: enroll the creator → creator submits their gift details → reward becomes ready to send → your OMS places the order → report order placement → report shipment. Use the gift/reward ID throughout this flow; the creator, campaign, and OMS order each have their own IDs. ### Before you start First, [import your product catalog](https://docs.superfiliate.com/docs/import-products) through the API and configure the available variants in your merchant-managed gifting campaign. The import guide covers product IDs, variants, and keeping the catalog updated. Create server-side credentials with `gifting_rewards.write` (which includes reward reads) and `webhooks.write` if subscribing to updates. Keep credentials on your backend; see [API Keys](https://docs.superfiliate.com/docs/api-keys). > Beta merchant-managed fulfillment > > The fulfillment endpoint is Beta and supports merchant-managed campaigns. It records fulfillment; it does not place an order or ship a package. Shopify-managed gifts use their Shopify fulfillment flow. ### Find gifts ready to send For merchant-managed gifting, follow [Add creators to gifting campaigns](https://docs.superfiliate.com/docs/gifting) to enroll the creator, and let them submit their address and product selections. Subscribe to `gifting_reward_updated` with [Create webhook](https://docs.superfiliate.com/reference/webhooks/create-webhook), or poll the ready-to-send rewards. The following subscription sends updates to your backend: **Subscribe to gift updates** ```bash curl -X POST "https://api.superfiliate.com/api/v1/webhooks" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourstore.com/webhooks/superfiliate/gifts", "topic": "gifting_reward_updated" }' ``` **Find gifts ready for your order system** ```bash curl "https://api.superfiliate.com/api/v1/gifting_rewards?gifting_stage=ready_to_send&page=1&items=25" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` This list returns an array and only includes merchant-managed gifts with submitted details. Add `campaign_id=345678` to restrict it to a campaign. For webhooks, [verify the signature](https://docs.superfiliate.com/docs/webhooks#verification), durably queue the work, then acknowledge with a `2xx` response. The delivery body is the reward object directly. Only queue order creation when `gifting_stage` is `ready_to_send`. Deliveries can repeat or arrive late, so check your saved order mapping before creating anything. For a delayed delivery, reconcile the reward’s current stage through the rewards list before placing an order; an old ready-to-send notification should not trigger fulfillment for a gift that has since been canceled or shipped. | Reward field | Use in your OMS integration | | --- | --- | | id | Superfiliate gift/reward ID. Use it in the fulfillment URL and as the key for preventing duplicate OMS orders. | | creator, shipping\_address | The recipient and delivery address. | | selected\_products | Use variant\_external\_id (or your mapped SKU) and quantity for each line. These are snapshots of the creator’s selections; map them to your own catalog IDs. | | fulfillment | Check for an existing external order reference before creating an order. Save the same mapping in your backend. | ### Create and register the gift order When polling, collect reward IDs from all pages into your work queue before advancing their stages; updating a filtered list while paging through it can skip records. For each ready-to-send reward, use its `shipping_address`, creator details, and `selected_products` to create a gift order in your OMS or fulfillment provider. Use the reward ID as your idempotency key there and save the resulting external order ID. Ensure concurrent workers cannot create two orders for the same reward. Once your OMS confirms creation, register that order in Superfiliate: **Record the order your system created** ```bash curl -X POST "https://api.superfiliate.com/api/v1/gifting_rewards/123456/fulfill" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "status": "order_placed", "external_provider": "your_oms", "external_order_id": "gift-order-123" }' ``` Here, `123456` is the Superfiliate reward ID and `gift-order-123` is your OMS order ID. For `status: order_placed`, both `external_provider` and `external_order_id` are required. Check the response has `gifting_stage: order_placed` and the expected order references under `fulfillment`. If the OMS order succeeds but this API request fails, retry registration with the saved order ID. Do not create another OMS order. Use a durable retry queue for network failures, rate limits, and server errors. Resolve validation errors before retrying; see [Errors](https://docs.superfiliate.com/docs/errors) and [Rate Limiting](https://docs.superfiliate.com/docs/rate-limiting). ### Record shipment After your provider confirms shipment, update the same reward with the actual carrier and tracking details. Always send `status` explicitly; omitting it defaults to `shipped`. **Record shipment** ```bash curl -X POST "https://api.superfiliate.com/api/v1/gifting_rewards/123456/fulfill" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "status": "shipped", "external_provider": "your_oms", "external_order_id": "gift-order-123", "carrier": "UPS", "tracking_number": "1Z999AA10123456784", "tracking_url": "https://www.ups.com/track/1Z999AA10123456784" }' ``` The [fulfillment endpoint](https://docs.superfiliate.com/reference/gifting/fulfill-gifting-reward) accepts merchant-managed rewards after creator details are submitted. Reuse the same order references on retries. Identical retries are supported; conflicting order references or changed details after shipment are rejected. Fulfillment updates also emit webhooks, so your handler should create orders only for ready-to-send gifts. A successful response records shipment in Superfiliate; your fulfillment provider remains responsible for shipping the package. ### Test gift fulfillment - Let a test creator submit their address and product choices. Confirm your integration receives the ready-to-send reward with those details. - Confirm one gift order is created in your OMS and its external order ID is recorded against the same Superfiliate reward. - Replay the webhook and retry the placement request. Confirm your OMS still has one order and Superfiliate keeps the same order reference. Simulate a failed registration after OMS creation and confirm retrying registers the existing order. - Send the shipment update and confirm the reward's fulfillment details contain the expected carrier and tracking information, with `gifting_stage: shipped`. Replay both the update and resulting webhook; confirm no extra order is created and the gift does not return to an earlier stage. --- ## Rate Limiting > A leaky bucket per store: 50 requests of burst, refilling at 2 per second. ### How it works We rate limit requests following a **leaky-bucket** strategy. Your bucket holds a burst of `50` requests, and it drains — recovering capacity — at `2` requests per second. In practice that means you can fire up to 50 requests instantly, then sustain a steady 2 requests per second forever. Go beyond the burst and the API responds with: **Over the limit** ```http HTTP/1.1 429 Too Many Requests ``` The bucket keeps draining while you back off, so a short pause is always enough to get going again. ### Handling 429s - **Retry with exponential backoff and jitter.** A 429 is not an error in your integration — it is the API asking you to slow down. - **Smooth your bursts.** If you sync in batches, queue requests at ~2 per second instead of firing them all at once. - **Fetch more per request.** List endpoints accept `items=250` — ten times the default page size. See [Pagination](https://docs.superfiliate.com/docs/pagination). - **Prefer webhooks to polling.** Webhooks push changes to you as they happen. See [Webhooks](https://docs.superfiliate.com/docs/webhooks). **Backoff with jitter** ```javascript const withBackoff = async (request, attempts = 5) => { for (let attempt = 0; attempt < attempts; attempt += 1) { const response = await request(); if (response.status !== 429) return response; const delay = 500 * 2 ** attempt + Math.random() * 250; await new Promise((resolve) => setTimeout(resolve, delay)); } throw new Error("Rate limited after retries"); }; ``` --- ## Pagination > Walk large collections with the page and items query parameters. ### The parameters Collections that can grow unbounded are paginated with two query parameters: | Parameter | Default | Notes | | --- | --- | --- | | `page` | `1` | 1-indexed page number. | | `items` | `25` | Records per page, up to a maximum of `250`. | ### Example Fetch the second hundred records from a collection: **Page 2, 100 per page** ```bash curl "https://api.superfiliate.com/api/v1/?page=2&items=100" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` --- ## Errors > Conventional status codes and a JSON envelope that always looks the same. ### The error envelope Every non-2xx response carries the same JSON shape: an `errors` array of human-readable strings. One request can fail for several reasons, so always treat it as a list: **Error envelope** ```json { "errors": ["Email is invalid"] } ``` ### Status codes | Status | Meaning | What to do | | --- | --- | --- | | `200 OK` | The request succeeded. | Endpoints that create or update return the resulting resource. | | `401 Unauthorized` | Credentials are missing or invalid. | Check the [Authorization header](https://docs.superfiliate.com/docs/authentication) format and that your pair is active. | | `403 Forbidden` | Valid credentials, missing scope. | Generate a new key with the scope shown on the endpoint's [reference page](https://docs.superfiliate.com/reference). | | `404 Not Found` | The resource does not exist on your store. | The envelope names the model, eg. `{ "errors": ["Customer not found"] }`. | | `422 Unprocessable` | The request was understood but failed validation. | Fix the parameters named in the messages and retry. | | `429 Too Many Requests` | You went over the rate limit. | Back off and retry — see [Rate limiting](https://docs.superfiliate.com/docs/rate-limiting). | | `5xx` | Something went wrong on our side. | Safe to retry with backoff; reach out if it persists. | ### Validation errors `422` messages are written to be actionable as-is. A few you may meet while integrating: | Message | Cause | | --- | --- | | `Missing customer params` | [Create customer](https://docs.superfiliate.com/reference/customers/create-customer) called without an `email`. | | `Email is invalid` | The email address failed format validation. | | `Value is invalid` | [Add balance](https://docs.superfiliate.com/reference/customers/add-balance) takes a value between `0.01` and `99.99`. | | `Unique source already used` | The `unique_identifier` was already spent for that customer — the reward was granted before, so this duplicate is safe to ignore. | | `Url is invalid` | [Create webhook](https://docs.superfiliate.com/reference/webhooks/create-webhook) needs a valid HTTPS URL. | --- ## Webhooks > Get customer changes pushed to your endpoint, verify signatures, and handle retries. ### Topics Five topics are available: | Topic | Fires when | Payload | | --- | --- | --- | | `customer_updated` | A customer is created or updated. | The full customer object — the same shape returned by the [customer endpoints](https://docs.superfiliate.com/reference/customers/search-customers). | | `gifting_reward_updated` | A creator submits gift details and product selections, or fulfillment details change. | The full merchant-managed gifting reward, including immutable product snapshots in `selected_products`. | | `discount_code_created` | A custom-commerce discount code is created. | The discount code object from the [discount code endpoint](https://docs.superfiliate.com/alpha/reference/discounts/get-discount-code). Requires `discounts.read` to subscribe. | | `discount_code_updated` | A custom-commerce discount code is updated. | The discount code object. Requires discounts.read to subscribe. | | `discount_code_deleted` | A custom-commerce discount code is deleted. | Snapshot of the discount code before deletion. Requires discounts.read to subscribe. | An example delivery: **POST · customer_updated** ```json { "id": 123345567, "email": "enrolled@superfiliate.com", "phone": "+12125551234", "first_name": "Alice", "last_name": "Wonder", "instagram_handle": "alice", "tiktok_handle": "alice_tiktok", "balance": "10.0", "code": "CODE-FOR-FRIENDS", "reward_code": "CODE-TO-REDEEM-BALANCE", "microsite_url": "https://shop.com/CODE-FOR-FRIENDS", "redeem_url": "https://shop.com/discount/CODE-TO-REDEEM-BALANCE?utm_...", "campaign_id": 7890, "enrolled_at": "2024-01-01 12:00:00 UTC", "tags": ["VIP Customer", "Newsletter Subscriber"] } ``` **POST · gifting_reward_updated** ```json { "id": 123456, "gifting_stage": "ready_to_send", "campaign": { "id": 345678, "name": "Summer gifting" }, "creator": { "id": 789012, "first_name": "Alex", "last_name": "Creator", "email": "alex@example.com", "phone": "+15555550123" }, "shipping_address": { "address1": "123 Main St", "city": "New York", "country_code": "US", "zip": "10001" }, "selected_products": [ { "product_external_id": "coffee-bundle", "variant_external_id": "coffee-dark", "product_title": "Coffee bundle", "variant_title": "Dark roast", "image_url": "https://example.com/dark-roast.jpg", "sku": "DARK-ROAST", "options": [{ "name": "Roast", "value": "Dark" }], "quantity": 1, "selected_at": "2026-08-11T18:00:00Z" } ], "fulfillment": null, "details_submitted_at": "2026-08-11T18:00:00Z", "created_at": "2026-08-10T18:00:00Z", "updated_at": "2026-08-11T18:00:00Z" } ``` ### Delivery & retries - Respond with a `2xx` quickly — do the heavy lifting after you acknowledge. - Anything else is retried multiple times using a randomized exponential backoff algorithm, for roughly 20 days before we give up. - Make your handler idempotent: deliveries can arrive more than once and out of order. Treat each payload as the latest known state of that resource, not as an event log. ### Headers | Header | Contents | | --- | --- | | `X-SUPERFILIATE-HMAC-SHA256` | Base64 HMAC signature of the raw request body, used for verification below. | | `X-SUPERFILIATE-SHOP-DOMAIN` | The store the event belongs to, eg. `example.com` — useful when one endpoint serves several stores. | | `X-SHOPIFY-SHOP-DOMAIN` | The Shopify domain for Shopify integrations, eg. example.myshopify.com. | ### Verify signatures Every delivery is signed with your `client_secret`. We compute an HMAC-SHA256 digest of the **raw** request body, Base64-encode it, then compare it with the signature header in constant time. > Verify the raw body > > Run verification on the body bytes exactly as received — before JSON parsing or any middleware rewrites them. A re-serialized body will almost never produce the same signature. **Ruby on Rails** ```ruby class WebhooksController < ApplicationController before_action :verify_webhook_authenticity! def index shop_domain = request.headers["X-SUPERFILIATE-SHOP-DOMAIN"] Rails.logger.info("Webhook received: #{shop_domain} #{request.raw_post}") end private def verify_webhook_authenticity! hmac_header = request.headers["X-SUPERFILIATE-HMAC-SHA256"] calculated_hmac = Base64.strict_encode64( OpenSSL::HMAC.digest("sha256", CLIENT_SECRET, request.raw_post) ) return if ActiveSupport::SecurityUtils.secure_compare(calculated_hmac, hmac_header) head :unauthorized end end ``` **Node.js** ```javascript import crypto from "node:crypto"; import express from "express"; const app = express(); app.post( "/webhooks/superfiliate", express.raw({ type: "application/json" }), (req, res) => { const signature = req.get("X-SUPERFILIATE-HMAC-SHA256") ?? ""; const digest = crypto .createHmac("sha256", process.env.SF_CLIENT_SECRET) .update(req.body) .digest("base64"); const expected = Buffer.from(digest); const received = Buffer.from(signature); const valid = expected.length === received.length && crypto.timingSafeEqual(expected, received); if (!valid) return res.status(401).end(); const customer = JSON.parse(req.body); // ...handle the update... res.status(200).end(); }, ); ``` **Python** ```python import base64 import hashlib import hmac import os from flask import Flask, abort, request app = Flask(__name__) @app.post("/webhooks/superfiliate") def superfiliate_webhook(): signature = request.headers.get("X-Superfiliate-Hmac-Sha256", "") digest = hmac.new( os.environ["SF_CLIENT_SECRET"].encode(), request.get_data(), # the raw body, before any parsing hashlib.sha256, ).digest() expected = base64.b64encode(digest).decode() if not hmac.compare_digest(expected, signature): abort(401) return "", 200 ``` ### Manage subscriptions Subscriptions are managed entirely through the API: - [GETList webhooks](https://docs.superfiliate.com/reference/webhooks/list-webhooks) - [POSTCreate webhook](https://docs.superfiliate.com/reference/webhooks/create-webhook) - [DELRemove webhook](https://docs.superfiliate.com/reference/webhooks/delete-webhook) --- ## GET /api/dry-run/v1/attribution — Get attribution installation Reference: https://docs.superfiliate.com/agents/reference/attribution/dry-run-get-attribution-installation Returns the shop-specific script tag used to install Superfiliate attribution tracking. ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/attribution" \ -H "Authorization: Bearer superfiliate-dry-run-agent" ``` ### Responses - `200` — The attribution installation - `401` — Unauthorized - `403` — Missing required scope Example `200` response: ```json { "scriptTag": "", "version": "1" } ``` --- ## GET /api/dry-run/v1/campaigns — List campaigns Reference: https://docs.superfiliate.com/agents/reference/campaigns/dry-run-list-campaigns ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | no | — | ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/campaigns" \ -H "Authorization: Bearer superfiliate-dry-run-agent" ``` ### Responses - `200` — A list of campaigns - `401` — Unauthorized - `403` — Missing required scope Example `200` response: ```json [ { "id": 123123, "name": "Wide Audience - Loyalty Program", "status": "active", "audience_type": "loyalty" }, { "id": 456456, "name": "Cat Owners Instagram - 2022", "status": "finished", "audience_type": "creators" } ] ``` --- ## GET /api/dry-run/v1/conversions — Search for conversions Reference: https://docs.superfiliate.com/agents/reference/conversions/dry-run-search-conversions ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `code` | string | no | — | | `start_date` | string | no | Defaults to 7 days ago | | `end_date` | string | no | Defaults to the current time | | `campaign_id` | integer | no | — | | `superfiliate_customer_id` | integer | no | — | | `include_pending` | boolean | no | When true, includes pending conversions in the response. Defaults to `false` | | `page` | integer | no | Default is `1` | | `items` | integer | no | Default is `25` and maximum is `250` | ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/conversions?start_date=2026-01-01&items=50" \ -H "Authorization: Bearer superfiliate-dry-run-agent" ``` ### Responses - `200` — A list of conversions - `401` — Unauthorized - `403` — Missing required scope Example `200` response: ```json [ { "id": 123345567, "code": "VFEIJO10", "status": "finished", "total_value": "94.5", "subtotal_value": "82.3", "created_at": "2025-02-19 13:13:59 UTC", "commission": "9.45", "campaign_name": "Program 1", "campaign_id": 123456, "superfiliate_customer_id": 789012, "order_id": "123456" } ] ``` --- ## POST /api/dry-run/v1/conversions/{id}/reject — Reject a conversion Reference: https://docs.superfiliate.com/agents/reference/conversions/dry-run-reject-conversion Rejects one conversion, with the same rules and side effects as **Reject conversion** in the Superfiliate app. The commission credited for the conversion is removed from the creator's wallet, without taking the wallet below zero. Commission already paid out is not recovered. The conversion stops counting toward promotions and the creator's stats, any usage fee charged for it is refunded as an app credit, and for Shopify orders the order's Superfiliate tag is updated. A conversion can't be rejected when it is already rejected, was manually approved, or its creator is banned. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | Conversion ID | ### Request body - `rejection_reason` (string, required) — Why the conversion is rejected. Allowed values: `self_conversion`, `test_conversion`, `banned_superfiliate`, `code_leak`. ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/conversions/1234567890/reject" \ -X POST \ -H "Authorization: Bearer superfiliate-dry-run-agent" \ -H "Content-Type: application/json" \ -d '{ "rejection_reason": "code_leak" }' ``` ### Responses - `200` — The rejected conversion - `401` — Unauthorized - `403` — Missing required scope - `404` — Conversion not found - `422` — Invalid rejection reason or conversion can't be rejected Example `200` response: ```json { "id": 123345567, "status": "rejected", "rejection_reason": "code_leak" } ``` --- ## GET /api/dry-run/v1/customers — Search for customers Reference: https://docs.superfiliate.com/agents/reference/customers/dry-run-search-customers ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | no | — | | `email` | string | no | — | | `enrolled` | boolean | no | — | | `enrolled_start_date` | string | no | — | | `enrolled_end_date` | string | no | — | | `page` | integer | no | Default is `1` | | `items` | integer | no | Default is `25` and maximum is `250` | ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/customers?email=alice%40example.com" \ -H "Authorization: Bearer superfiliate-dry-run-agent" ``` ### Responses - `200` — A list of customers - `401` — Unauthorized - `403` — Missing required scope Example `200` response: ```json [ { "id": 234456678, "email": "not-enrolled@superfiliate.com", "phone": null, "first_name": "Bob", "last_name": "Builder", "instagram_handle": null, "tiktok_handle": null, "balance": "12.3", "code": null, "reward_code": null, "microsite_url": null, "redeem_url": null, "campaign_id": null, "enrolled_at": null, "tags": [ "Potential Influencer" ] } ] ``` --- ## POST /api/dry-run/v1/customers — Create customer Reference: https://docs.superfiliate.com/agents/reference/customers/dry-run-create-customer Creates a customer when the email is new. If the customer already exists, supplied non-blank profile fields update the existing customer before it is returned. ### Request body - `email` (string, required) - `first_name` (string) — Single-line name; whitespace is normalized before persistence. HTTP(S) URLs and control characters are rejected. - `last_name` (string) — Single-line name; whitespace is normalized before persistence. HTTP(S) URLs and control characters are rejected. - `instagram_handle` (string) - `tiktok_handle` (string) - `phone` (string) ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/customers" \ -X POST \ -H "Authorization: Bearer superfiliate-dry-run-agent" \ -H "Content-Type: application/json" \ -d '{ "email": "new@customer.com", "first_name": "John", "last_name": "Doe", "phone": "+12125551234" }' ``` ### Responses - `200` — The customer. If the email already existed, the existing customer is returned unchanged. - `401` — Unauthorized - `403` — Missing required scope - `422` — Invalid parameters Example `200` response: ```json { "id": 234456678, "email": "not-enrolled@superfiliate.com", "phone": null, "first_name": "Bob", "last_name": "Builder", "instagram_handle": null, "tiktok_handle": null, "balance": "12.3", "code": null, "reward_code": null, "microsite_url": null, "redeem_url": null, "campaign_id": null, "enrolled_at": null, "tags": [ "Potential Influencer" ] } ``` --- ## PUT /api/dry-run/v1/customers/{id} — Update customer Reference: https://docs.superfiliate.com/agents/reference/customers/dry-run-update-customer Updates only the supplied code, first_name, last_name, email, phone, instagram_handle, and tiktok_handle. Omitted fields stay unchanged. Social handles use the same linking behavior as Create customer: Instagram replaces the linked profile, while TikTok adds a profile to the customer's linked profiles. Names use the same first_name and last_name fields as customer creation; empty strings clear them. Email is trimmed and lowercased. Codes are uppercased and must be available, 2–30 characters long, using only A–Z, 0–9, hyphens, and underscores. Protected codes are rejected. Changing a code requires an enrolled, active customer with an existing code. It replaces the current code using the program's reward settings and removes the old code and redirects. Sending the current code again leaves it unchanged. Profile-only updates do not require enrollment. Requires customers.write. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | — | ### Request body - `code` (string) - `first_name` (string) - `last_name` (string) - `email` (string) - `instagram_handle` (string) - `tiktok_handle` (string) - `phone` (string) — Normalized to E.164. Send an empty string to clear; invalid numbers return 422. ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/customers/1234567890" \ -X PUT \ -H "Authorization: Bearer superfiliate-dry-run-agent" \ -H "Content-Type: application/json" \ -d '{ "code": "ALICE20", "first_name": "Alice", "last_name": "Wonder", "email": "alice@example.com", "instagram_handle": "alice", "tiktok_handle": "alice_tiktok", "phone": "+12125551234" }' ``` ### Responses - `200` — Customer updated - `401` — Unauthorized - `403` — Missing required scope - `404` — Customer not found - `422` — Invalid update Example `200` response: ```json { "id": 123345567, "email": "enrolled@superfiliate.com", "phone": "+12125551234", "first_name": "Alice", "last_name": "Wonder", "instagram_handle": "alice", "tiktok_handle": "alice_tiktok", "balance": "10.0", "code": "ALICE20", "reward_code": "CODE-TO-REDEEM-BALANCE", "microsite_url": "https://shop.com/CODE-FOR-FRIENDS", "redeem_url": "https://shop.com/discount/CODE-TO-REDEEM-BALANCE?utm_...", "campaign_id": 7890, "enrolled_at": "2024-01-01 12:00:00 UTC", "tags": [ "VIP Customer", "Newsletter Subscriber" ] } ``` --- ## POST /api/dry-run/v1/customers/{id}/enroll — Enroll a customer Reference: https://docs.superfiliate.com/agents/reference/customers/dry-run-enroll-customer Enrolls a customer to a campaign, if not already enrolled to a campaign. Pass `campaign_id` to enroll the customer in any campaign that belongs to the shop. If omitted, the shop's default campaign is used. If the customer is already enrolled to a campaign, it just returns the customer data. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | — | ### Request body - `code` (string) - `campaign_id` (integer) — ID of any campaign that belongs to the shop. If omitted, the shop's default campaign is used. - `code_activation` (string) — The code strategy to use. "create" will try to create a new code for the customer, using the specified code. If not available, it will append random characters until the code is available. "link" will ignore code's creation and just assume the code already exists in Shopify. "override" will try to create the specified code. If the specified code is not available, it will delete that existing code and create under the campaign settings.If this option is not specified, it will default to "create". Allowed values: `create`, `link`, `override`. ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/customers/1234567890/enroll" \ -X POST \ -H "Authorization: Bearer superfiliate-dry-run-agent" \ -H "Content-Type: application/json" \ -d '{ "code": "CUSTOM-CODE", "campaign_id": 123123123 }' ``` ### Responses - `200` — The enrolled customer - `401` — Unauthorized - `403` — Missing required scope - `404` — Not found Example `200` response: ```json { "id": 123345567, "email": "enrolled@superfiliate.com", "phone": "+12125551234", "first_name": "Alice", "last_name": "Wonder", "instagram_handle": "alice", "tiktok_handle": "alice_tiktok", "balance": "10.0", "code": "CODE-FOR-FRIENDS", "reward_code": "CODE-TO-REDEEM-BALANCE", "microsite_url": "https://shop.com/CODE-FOR-FRIENDS", "redeem_url": "https://shop.com/discount/CODE-TO-REDEEM-BALANCE?utm_...", "campaign_id": 7890, "enrolled_at": "2024-01-01 12:00:00 UTC", "tags": [ "VIP Customer", "Newsletter Subscriber" ] } ``` --- ## POST /api/dry-run/v1/customers/{id}/add_balance — Add balance Reference: https://docs.superfiliate.com/agents/reference/customers/dry-run-add-balance Adds balance to a customer. A "unique identifier" needs to be sent with it, it can be any string internal to you that we can use to guarantee that the balance is only added once. If the same unique identifier is sent twice, for the same customer, the second request will fail. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | — | ### Request body - `value` (string, required) — Between `0.01` and `99.99`. - `unique_identifier` (string, required) — To avoid duplicate rewards. ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/customers/1234567890/add_balance" \ -X POST \ -H "Authorization: Bearer superfiliate-dry-run-agent" \ -H "Content-Type: application/json" \ -d '{ "value": "5.55", "unique_identifier": "MyInternalRewardEvent;12345" }' ``` ### Responses - `200` — Balance added - `401` — Unauthorized - `403` — Missing required scope - `404` — Customer not found - `422` — Invalid params Example `200` response: ```json { "id": 234456678, "email": "not-enrolled@superfiliate.com", "phone": null, "first_name": "Bob", "last_name": "Builder", "instagram_handle": null, "tiktok_handle": null, "balance": "12.3", "code": null, "reward_code": null, "microsite_url": null, "redeem_url": null, "campaign_id": null, "enrolled_at": null, "tags": [ "Potential Influencer" ] } ``` --- ## GET /api/dry-run/v1/customers/{id}/insights — Get customer insights Reference: https://docs.superfiliate.com/agents/reference/customers/dry-run-get-customer-insights ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | — | ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `date_range` | object | no | — | ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/customers/1234567890/insights" \ -H "Authorization: Bearer superfiliate-dry-run-agent" ``` ### Responses - `200` — Customer insights - `401` — Unauthorized - `403` — Missing required scope --- ## POST /api/dry-run/v1/customers/{id}/redact — Redact a customer Reference: https://docs.superfiliate.com/agents/reference/customers/dry-run-redact-customer Redacts the customer's personal data and records the redaction request for future synchronization. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | — | ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/customers/1234567890/redact" \ -X POST \ -H "Authorization: Bearer superfiliate-dry-run-agent" ``` ### Responses - `200` — Customer redacted - `401` — Unauthorized - `403` — Missing required scope - `404` — Customer not found - `422` — Customer could not be redacted Example `200` response: ```json { "success": true } ``` --- ## GET /api/dry-run/v1/gifting_campaigns — List gifting campaigns Reference: https://docs.superfiliate.com/agents/reference/gifting/dry-run-list-gifting-campaigns Lists seeding campaigns belonging to your shop, including campaigns without submitted gifts. These are gifting campaigns, separate from the loyalty and creator programs returned by `/api/v1/campaigns`. Use the returned `id` as `campaign_id` when listing gifting rewards. Both Shopify and merchant-managed campaigns are included; filter by `fulfillment_mode=merchant_managed` for external OMS fulfillment. The gifting rewards API only supports merchant-managed campaigns. `available_products` contains products and their eligible variants, with internal and external IDs, titles, SKUs, images, and option values. Merchant-managed campaigns use their configured variants; depleted campaign quantities and unavailable catalog entries are excluded. Shopify campaigns use their configured product and Shopify collections, excluding unavailable variants. Quantity caps and remaining quantities are campaign-wide and are null for uncapped variants. Availability is a snapshot, not a reservation; campaign and category selection limits still apply. Results include active and inactive campaigns by default and are ordered by creation time, then ID, descending. A campaign is active when neither its expiration nor archive date has passed. Pagination returns an array; request successive pages until an empty array is returned. Production requests require `gifting_campaigns.read`, which must be explicitly granted to your API key. ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | no | Only return the gifting campaign with this ID | | `active` | boolean | no | Only return active or inactive campaigns | | `fulfillment_mode` | string | no | Only return campaigns using this fulfillment mode | | `page` | integer | no | Default is `1`; values below `1` use `1` | | `items` | integer | no | Default is `25`; values are clamped between `1` and `250` | ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/gifting_campaigns?active=true&fulfillment_mode=merchant_managed&page=1&items=25" \ -H "Authorization: Bearer superfiliate-dry-run-agent" ``` ### Responses - `200` — A list of gifting campaigns - `401` — Unauthorized - `403` — Missing required scope - `422` — Invalid filters Example `200` response: ```json [ { "id": 345678, "name": "Summer gifting", "active": true, "fulfillment_mode": "merchant_managed", "redeem_method": "manual", "multi_product_selection_limit": 2, "expires_at": null, "archived_at": null, "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z", "available_products": [ { "id": 123456, "external_id": "t-shirt", "title": "T-shirt", "variants": [ { "id": 234567, "external_id": "t-shirt-black-medium", "title": "Black / Medium", "sku": "TSHIRT-BLK-M", "image_url": null, "options": [ { "name": "Color", "value": "Black" }, { "name": "Size", "value": "Medium" } ], "available_quantity": 100, "remaining_quantity": 99 } ] } ] } ] ``` --- ## POST /api/dry-run/v1/gifting_campaigns/{id}/enroll — Enroll a customer in a gifting campaign Reference: https://docs.superfiliate.com/agents/reference/gifting/dry-run-enroll-customer-in-gifting-campaign Enrolls an existing Superfiliate customer in an active seeding/gifting campaign. Use a gifting campaign ID from `GET /api/v1/gifting_campaigns`, not a program ID from `/campaigns`. Affiliate program enrollment is not required. Shopify and merchant-managed campaigns are supported. Repeating a request returns the customer's latest existing gift in this campaign, including claimed, expired, or canceled gifts, without changing it or sending another gift. Recurrence remains managed by the campaign's existing schedule; this endpoint does not manually refresh gifts. New manual gifts are unlocked. Automatic Shopify gifts enter `ready_to_send` when an address exists, or `missing_address` otherwise. Adding a customer does not immediately place a Shopify order. Existing gifting automations run for new additions only. Requires explicitly granted `gifting_campaigns.write`; existing API keys gain no new permission. Returns 404 for a customer or campaign outside the credential's shop, and 422 for inactive campaigns. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | Superfiliate gifting campaign ID | ### Request body - `customer_id` (integer, required) — Superfiliate customer ID. ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/gifting_campaigns/1234567890/enroll" \ -X POST \ -H "Authorization: Bearer superfiliate-dry-run-agent" \ -H "Content-Type: application/json" \ -d '{ "customer_id": 789012 }' ``` ### Responses - `200` — The new or existing gift - `401` — Unauthorized - `403` — Missing required scope - `404` — Customer or gifting campaign not found - `422` — Invalid input or inactive campaign Example `200` response: ```json { "id": 123456, "customer_id": 789012, "gifting_campaign_id": 345678, "status": "unlocked" } ``` --- ## GET /api/dry-run/v1/gifting_rewards — List gifting rewards Reference: https://docs.superfiliate.com/agents/reference/gifting/dry-run-list-gifting-rewards Lists creator details submitted for merchant-managed gifting campaigns. Use the campaign and creator IDs to create and fulfill the gifting reward in your OMS. ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `campaign_id` | integer | no | Only return gifting rewards from this campaign | | `gifting_stage` | string | no | Only return gifting rewards in this stage | | `page` | integer | no | Default is `1` | | `items` | integer | no | Default is `25` and maximum is `250` | ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/gifting_rewards?campaign_id=345678&gifting_stage=ready_to_send&page=1&items=25" \ -H "Authorization: Bearer superfiliate-dry-run-agent" ``` ### Responses - `200` — A list of gifting rewards - `401` — Unauthorized - `403` — Missing required scope - `422` — Invalid gifting stage Example `200` response: ```json [ { "id": 123456, "gifting_stage": "ready_to_send", "campaign": { "id": 345678, "name": "Summer gifting" }, "creator": { "id": 789012, "first_name": "Alex", "last_name": "Creator", "email": "alex@example.com", "phone": "+15555550123" }, "shipping_address": { "address1": "123 Main St", "address2": "Apt 4", "city": "New York", "country_code": "US", "province_code": "NY", "zip": "10001" }, "selected_products": [ { "product_external_id": "coffee-bundle", "variant_external_id": "coffee-bundle-dark-roast", "product_title": "Coffee bundle", "variant_title": "Dark roast", "image_url": "https://example.com/dark-roast.jpg", "sku": "DARK-ROAST", "options": [ { "name": "Roast", "value": "Dark" } ], "quantity": 1, "selected_at": "2026-08-11T18:00:00Z" } ], "fulfillment": null, "details_submitted_at": "2026-08-11T18:00:00Z", "created_at": "2026-08-10T18:00:00Z", "updated_at": "2026-08-11T18:00:00Z" } ] ``` --- ## POST /api/dry-run/v1/gifting_rewards/{id}/fulfill — Fulfill a gifting reward (Beta) Reference: https://docs.superfiliate.com/agents/reference/gifting/dry-run-fulfill-gifting-reward Records fulfillment for a merchant-managed gift after the creator submits their details. Use `status: order_placed` with `external_order_id` and `external_provider` to register an order before shipping. Later send `status: shipped` with tracking details. The registered order references are retained when omitted; conflicting references are rejected. Omitting `status` preserves the existing shipped behavior. Identical retries do not change timestamps or regress shipped/delivered gifts. Once shipped, different details are rejected. Campaigns using Shopify-managed fulfillment are not available through this endpoint. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | Gifting reward ID | ### Request body - `status` (string) — Allowed values: `order_placed`, `shipped`. - `external_provider` (string) — Provider name (e.g. shopify). Required for order_placed. - `external_order_id` (string) — Order ID in the merchant's OMS. - `carrier` (string) — Shipping carrier. - `tracking_number` (string) — Carrier tracking number. - `tracking_url` (string) — HTTP(S) tracking URL. ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/gifting_rewards/1234567890/fulfill" \ -X POST \ -H "Authorization: Bearer superfiliate-dry-run-agent" \ -H "Content-Type: application/json" \ -d '{ "status": "shipped", "external_provider": "shopify", "external_order_id": "order-123", "carrier": "UPS", "tracking_number": "1Z999AA10123456784", "tracking_url": "https://www.ups.com/track/1Z999AA10123456784" }' ``` ### Responses - `200` — Order placed before shipping - `401` — Unauthorized - `403` — Missing required scope - `404` — Gifting reward not found - `422` — Gifting reward cannot be fulfilled Example `200` response: ```json { "id": 123456, "gifting_stage": "shipped", "campaign": { "id": 345678, "name": "Summer gifting" }, "creator": { "id": 789012, "first_name": "Alex", "last_name": "Creator", "email": "alex@example.com", "phone": "+15555550123" }, "shipping_address": { "address1": "123 Main St", "address2": "Apt 4", "city": "New York", "country_code": "US", "province_code": "NY", "zip": "10001" }, "selected_products": [ { "product_external_id": "coffee-bundle", "variant_external_id": "coffee-bundle-dark-roast", "product_title": "Coffee bundle", "variant_title": "Dark roast", "image_url": "https://example.com/dark-roast.jpg", "sku": "DARK-ROAST", "options": [ { "name": "Roast", "value": "Dark" } ], "quantity": 1, "selected_at": "2026-08-11T18:00:00Z" } ], "fulfillment": { "external_order_id": "order-123", "carrier": "UPS", "tracking_number": "1Z999AA10123456784", "tracking_url": "https://www.ups.com/track/1Z999AA10123456784", "fulfilled_at": "2026-08-13T18:00:00Z" }, "details_submitted_at": "2026-08-11T18:00:00Z", "created_at": "2026-08-10T18:00:00Z", "updated_at": "2026-08-11T18:00:00Z" } ``` --- ## GET /api/dry-run/v1/products — List products Reference: https://docs.superfiliate.com/agents/reference/products/dry-run-list-products Lists external OMS products and their variants for the authenticated shop. ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `page` | integer | no | Default is `1` | | `items` | integer | no | Default is `25` and maximum is `250` | ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/products?items=25&page=1" \ -H "Authorization: Bearer superfiliate-dry-run-agent" ``` ### Responses - `200` — A list of products - `401` — Unauthorized - `403` — Missing required scope Example `200` response: ```json [ { "id": 123456, "source": "custom_oms", "external_id": "t-shirt", "title": "Everyday T-shirt", "url": "https://store.example/products/t-shirt", "html_description": "

Soft cotton.

", "image_url": "https://cdn.example.com/products/t-shirt.jpg", "active": true, "variants": [] } ] ``` --- ## GET /api/dry-run/v1/products/{id} — Fetch product Reference: https://docs.superfiliate.com/agents/reference/products/dry-run-get-product Fetches one external OMS product and its variants for the authenticated shop. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | Superfiliate product ID | ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/products/1234567890" \ -H "Authorization: Bearer superfiliate-dry-run-agent" ``` ### Responses - `200` — The product - `401` — Unauthorized - `403` — Missing required scope - `404` — Product not found --- ## POST /api/dry-run/v1/products — Upsert product Reference: https://docs.superfiliate.com/agents/reference/products/dry-run-create-product Creates or updates an external OMS product by `source` and `external_id`. Included variants are independently upserted by their `external_id`; variants omitted from a request are left unchanged. Set `active` to `false` to disable a product or variant while retaining its history. ### Request body - `source` (string, required) — Stable source/provider key for this external catalog. - `external_id` (string, required) — Stable product identifier in the external OMS. - `title` (string, required) — Product display name. - `url` (string, required) — HTTP(S) URL for the external product. - `html_description` (string, nullable) — Optional HTML. Omit to preserve the current value; send null to clear it. - `image_url` (string, required) — HTTP(S) URL for product image. - `active` (boolean) — Defaults to `true` for a new product. - `variants` (array of object) — Variants to create or update. Omitted variants remain unchanged. - `external_id` (string, required) — Stable variant identifier. - `title` (string, required) — Variant display name. - `sku` (string, nullable) — Omit to preserve the current value; send null to clear it. - `image_url` (string, nullable) - `price` (string, nullable) — Optional decimal price in the authenticated shop's configured currency. - `active` (boolean) — Defaults to `true` for a new variant. - `options` (array of object) - `name` (string, required) - `value` (string, required) ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/products" \ -X POST \ -H "Authorization: Bearer superfiliate-dry-run-agent" \ -H "Content-Type: application/json" \ -d '{ "source": "custom_oms", "external_id": "everyday-t-shirt", "title": "Everyday T-shirt", "url": "https://store.example/products/t-shirt", "html_description": "

Soft cotton.

", "image_url": "https://cdn.example.com/products/t-shirt.jpg", "variants": [ { "external_id": "everyday-t-shirt-black-medium", "title": "Black / Medium", "sku": "TSHIRT-BLK-M", "price": "29.99", "options": [ { "name": "Color", "value": "Black" }, { "name": "Size", "value": "Medium" } ] } ] }' ``` ### Responses - `200` — Product upserted - `401` — Unauthorized - `403` — Missing required scope - `422` — Invalid parameters Example `200` response: ```json { "id": 123456, "source": "custom_oms", "external_id": "everyday-t-shirt", "title": "Everyday T-shirt", "url": "https://store.example/products/t-shirt", "html_description": "

Soft cotton.

", "image_url": "https://cdn.example.com/products/t-shirt.jpg", "active": true, "variants": [ { "id": 123457, "external_id": "everyday-t-shirt-black-medium", "title": "Black / Medium", "sku": "TSHIRT-BLK-M", "image_url": null, "price": "29.99", "active": true, "options": [ { "name": "Color", "value": "Black" }, { "name": "Size", "value": "Medium" } ] } ] } ``` --- ## POST /api/dry-run/v1/reports — Create custom report Reference: https://docs.superfiliate.com/agents/reference/reports/dry-run-create-report Creates a custom report that appears in the reporting UI. `source` selects the report type. `filters` uses the same camel-cased structure as the reporting UI. Omitted filter properties default to an unfiltered report. ### Request body - `name` (string, required) — Must be unique among the shop's custom reports. - `source` (string, required) — The dataset used by the report. Allowed values: `conversions`, `superfiliates`. - `filters` (object) - `hiddenColumnIds` (array of string) — Column identifiers hidden in the report. - `programIds` (array of string) — Program identifiers included in the report. - `superbriefIds` (array of string) — SuperBrief identifiers included in the report. Applies to conversions reports. - `creatorSources` (boolean) — Include conversions without a program or SuperBrief source. Applies to conversions reports. - `customerIds` (array of string) — Customer identifiers included in the report. - `tagIds` (array of string) — Customer tag identifiers included in the report. - `dateRange` (object) — Absolute or relative reporting period. - `startDate` (one of) - variant 1 (string) — An absolute date in YYYY-MM-DD format. - variant 2 (object) - `unit` (string) — Allowed values: `days`. - `value` (number) - variant 3 (object) - `rule` (string) — Allowed values: `week_to_date`, `month_to_date`, `quarter_to_date`, `year_to_date`. - `endDate` (one of) - variant 1 (string) — An absolute date in YYYY-MM-DD format. - variant 2 (object) - `unit` (string) — Allowed values: `days`. - `value` (number) - variant 3 (object) - `rule` (string) — Allowed values: `week_to_date`, `month_to_date`, `quarter_to_date`, `year_to_date`. - `advancedFilters` (array of object) — Column-level filters. Values for `is_in` and `is_not_in` may be arrays. - `column` (string, required) - `condition` (string, required) — Allowed values: `contains`, `does_not_contain`, `ends_with`, `greater_than`, `greater_than_or_equal_to`, `is`, `is_empty`, `is_not`, `is_not_empty`, `is_in`, `is_not_in`, `less_than`, … (+2 more). - `value` (object, nullable) — Value to compare. Omit for empty/not-empty conditions. - `columnDateRange` (object) — Absolute or relative period for one date column, such as `created_at`. - `column` (string, required) - `startDate` (one of) - variant 1 (string) — An absolute date in YYYY-MM-DD format. - variant 2 (object) - `unit` (string) — Allowed values: `days`. - `value` (number) - variant 3 (object) - `rule` (string) — Allowed values: `week_to_date`, `month_to_date`, `quarter_to_date`, `year_to_date`. - `endDate` (one of) - variant 1 (string) — An absolute date in YYYY-MM-DD format. - variant 2 (object) - `unit` (string) — Allowed values: `days`. - `value` (number) - variant 3 (object) - `rule` (string) — Allowed values: `week_to_date`, `month_to_date`, `quarter_to_date`, `year_to_date`. ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/reports" \ -X POST \ -H "Authorization: Bearer superfiliate-dry-run-agent" \ -H "Content-Type: application/json" \ -d '{ "name": "Monthly creator performance", "source": "superfiliates", "filters": { "programIds": [ "12345" ], "dateRange": { "startDate": { "rule": "month_to_date" }, "endDate": { "unit": "days", "value": 0 } } } }' ``` ### Responses - `200` — Custom report created - `401` — Unauthorized - `403` — Missing required scope - `422` — Invalid params Example `200` response: ```json { "id": 123123123, "name": "Monthly creator performance", "source": "superfiliates", "filters": { "hiddenColumnIds": [ "customer_phone" ], "programIds": [ "12345" ], "customerIds": [], "tagIds": [ "67890" ], "dateRange": { "startDate": { "rule": "month_to_date" }, "endDate": { "unit": "days", "value": 0 } }, "advancedFilters": [ { "column": "state", "condition": "is", "value": "Enrolled" } ], "columnDateRange": { "column": "created_at", "startDate": "2026-01-01", "endDate": "2026-01-31" } }, "is_custom": true, "created_at": "2026-01-31T12:00:00Z", "updated_at": "2026-01-31T12:00:00Z" } ``` --- ## GET /api/dry-run/v1/reports — List reports Reference: https://docs.superfiliate.com/agents/reference/reports/dry-run-list-reports Lists all saved reports for the authenticated shop. ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/reports" \ -H "Authorization: Bearer superfiliate-dry-run-agent" ``` ### Responses - `200` — A list of saved reports - `401` — Unauthorized - `403` — Missing required scope Example `200` response: ```json [ { "id": 123123123, "name": "Monthly creator performance", "source": "superfiliates", "filters": { "hiddenColumnIds": [ "customer_phone" ], "programIds": [ "12345" ], "customerIds": [], "tagIds": [ "67890" ], "dateRange": { "startDate": { "rule": "month_to_date" }, "endDate": { "unit": "days", "value": 0 } }, "advancedFilters": [ { "column": "state", "condition": "is", "value": "Enrolled" } ], "columnDateRange": { "column": "created_at", "startDate": "2026-01-01", "endDate": "2026-01-31" } }, "is_custom": true, "created_at": "2026-01-31T12:00:00Z", "updated_at": "2026-01-31T12:00:00Z" } ] ``` --- ## GET /api/dry-run/v1/reports/{id}/data — Fetch report data Reference: https://docs.superfiliate.com/agents/reference/reports/dry-run-fetch-report-data Runs a saved report and returns its rows. Saved filters and hidden columns are applied. Superfiliate rows include `country_code` (ISO 3166-1 alpha-2), `city`, `province_code`, and `zip` when those columns are visible. Relative dates are resolved in the shop's timezone. Results are paginated with 25 rows per page by default. `has_more` and `next_page` indicate whether and where to request the next page. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | Saved report ID | ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `page` | integer | no | Default is `1` | | `items` | integer | no | Default is `25` and maximum is `250` | ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/reports/1234567890/data?page=1&items=25" \ -H "Authorization: Bearer superfiliate-dry-run-agent" ``` ### Responses - `200` — Report data - `401` — Unauthorized - `403` — Missing required scope - `404` — Report not found - `422` — Invalid saved filters Example `200` response: ```json { "data": [ { "id": 123345567, "code": "CREATOR10", "status": "finished", "total_value": "94.5", "program_name": "Ambassador program" } ], "has_more": false, "next_page": null } ``` --- ## GET /api/dry-run/v1/webhooks — List webhooks Reference: https://docs.superfiliate.com/agents/reference/webhooks/dry-run-list-webhooks ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/webhooks" \ -H "Authorization: Bearer superfiliate-dry-run-agent" ``` ### Responses - `200` — A list of webhooks - `401` — Unauthorized - `403` — Missing required scope Example `200` response: ```json [ { "id": 123123123, "url": "https://yourapp.com/webhooks/superfiliate/customer_updated", "topic": "customer_updated" }, { "id": 123123123, "url": "https://yourapp.com/webhooks/superfiliate/customer_updated", "topic": "customer_updated" }, { "id": 123123123, "url": "https://yourapp.com/webhooks/superfiliate/customer_updated", "topic": "customer_updated" } ] ``` --- ## POST /api/dry-run/v1/webhooks — Create webhook Reference: https://docs.superfiliate.com/agents/reference/webhooks/dry-run-create-webhook ### Request body - `url` (string, required) - `topic` (string, required) — The event to subscribe to. Allowed values: `customer_updated`, `gifting_reward_updated`, `discount_code_created`, `discount_code_updated`, `discount_code_deleted`. ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/webhooks" \ -X POST \ -H "Authorization: Bearer superfiliate-dry-run-agent" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourapp.com/webhooks/superfiliate/customer_updated", "topic": "customer_updated" }' ``` ### Responses - `200` — Webhook created - `401` — Unauthorized - `403` — Missing required scope - `422` — Invalid params Example `200` response: ```json { "id": 123123123, "url": "https://yourapp.com/webhooks/superfiliate/customer_updated", "topic": "customer_updated" } ``` --- ## DELETE /api/dry-run/v1/webhooks/{id} — Remove webhook Reference: https://docs.superfiliate.com/agents/reference/webhooks/dry-run-delete-webhook ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | — | ### Example request ```bash curl "https://api.superfiliate.com/api/dry-run/v1/webhooks/1234567890" \ -X DELETE \ -H "Authorization: Bearer superfiliate-dry-run-agent" ``` ### Responses - `200` — Webhook removed - `401` — Unauthorized - `403` — Missing required scope - `404` — Webhook not found Example `200` response: ```json { "id": 123123123, "url": "https://yourapp.com/webhooks/superfiliate/customer_updated", "topic": "customer_updated" } ``` --- ## GET /api/v1/attribution — Get attribution installation Reference: https://docs.superfiliate.com/reference/attribution/get-attribution-installation Required scope: `attribution.read` Returns the shop-specific script tag used to install Superfiliate attribution tracking. ### Example request ```bash curl "https://api.superfiliate.com/api/v1/attribution" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` ### Responses - `200` — The attribution installation - `401` — Unauthorized - `403` — Missing required scope Example `200` response: ```json { "scriptTag": "", "version": "1" } ``` --- ## GET /api/v1/campaigns — List campaigns Reference: https://docs.superfiliate.com/reference/campaigns/list-campaigns Required scope: `campaigns.read` ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | no | — | ### Example request ```bash curl "https://api.superfiliate.com/api/v1/campaigns" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` ### Responses - `200` — A list of campaigns - `401` — Unauthorized - `403` — Missing required scope Example `200` response: ```json [ { "id": 123123, "name": "Wide Audience - Loyalty Program", "status": "active", "audience_type": "loyalty" }, { "id": 456456, "name": "Cat Owners Instagram - 2022", "status": "finished", "audience_type": "creators" } ] ``` --- ## GET /api/v1/conversions — Search for conversions Reference: https://docs.superfiliate.com/reference/conversions/search-conversions Required scope: `conversions.read` ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `code` | string | no | — | | `start_date` | string | no | Defaults to 7 days ago | | `end_date` | string | no | Defaults to the current time | | `campaign_id` | integer | no | — | | `superfiliate_customer_id` | integer | no | — | | `include_pending` | boolean | no | When true, includes pending conversions in the response. Defaults to `false` | | `page` | integer | no | Default is `1` | | `items` | integer | no | Default is `25` and maximum is `250` | ### Example request ```bash curl "https://api.superfiliate.com/api/v1/conversions?start_date=2026-01-01&items=50" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` ### Responses - `200` — A list of conversions - `401` — Unauthorized - `403` — Missing required scope Example `200` response: ```json [ { "id": 123345567, "code": "VFEIJO10", "status": "finished", "total_value": "94.5", "subtotal_value": "82.3", "created_at": "2025-02-19 13:13:59 UTC", "commission": "9.45", "campaign_name": "Program 1", "campaign_id": 123456, "superfiliate_customer_id": 789012, "order_id": "123456" } ] ``` --- ## POST /api/v1/conversions/{id}/reject — Reject a conversion Reference: https://docs.superfiliate.com/reference/conversions/reject-conversion Required scope: `conversions.write` Rejects one conversion, with the same rules and side effects as **Reject conversion** in the Superfiliate app. The commission credited for the conversion is removed from the creator's wallet, without taking the wallet below zero. Commission already paid out is not recovered. The conversion stops counting toward promotions and the creator's stats, any usage fee charged for it is refunded as an app credit, and for Shopify orders the order's Superfiliate tag is updated. A conversion can't be rejected when it is already rejected, was manually approved, or its creator is banned. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | Conversion ID | ### Request body - `rejection_reason` (string, required) — Why the conversion is rejected. Allowed values: `self_conversion`, `test_conversion`, `banned_superfiliate`, `code_leak`. ### Example request ```bash curl "https://api.superfiliate.com/api/v1/conversions/1234567890/reject" \ -X POST \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "rejection_reason": "code_leak" }' ``` ### Responses - `200` — The rejected conversion - `401` — Unauthorized - `403` — Missing required scope - `404` — Conversion not found - `422` — Invalid rejection reason or conversion can't be rejected Example `200` response: ```json { "id": 123345567, "status": "rejected", "rejection_reason": "code_leak" } ``` --- ## GET /api/v1/customers — Search for customers Reference: https://docs.superfiliate.com/reference/customers/search-customers Required scope: `customers.read` ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | no | — | | `email` | string | no | — | | `enrolled` | boolean | no | — | | `enrolled_start_date` | string | no | — | | `enrolled_end_date` | string | no | — | | `page` | integer | no | Default is `1` | | `items` | integer | no | Default is `25` and maximum is `250` | ### Example request ```bash curl "https://api.superfiliate.com/api/v1/customers?email=alice%40example.com" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` ### Responses - `200` — A list of customers - `401` — Unauthorized - `403` — Missing required scope Example `200` response: ```json [ { "id": 234456678, "email": "not-enrolled@superfiliate.com", "phone": null, "first_name": "Bob", "last_name": "Builder", "instagram_handle": null, "tiktok_handle": null, "balance": "12.3", "code": null, "reward_code": null, "microsite_url": null, "redeem_url": null, "campaign_id": null, "enrolled_at": null, "tags": [ "Potential Influencer" ] } ] ``` --- ## POST /api/v1/customers — Create customer Reference: https://docs.superfiliate.com/reference/customers/create-customer Required scope: `customers.write` Creates a customer when the email is new. If the customer already exists, supplied non-blank profile fields update the existing customer before it is returned. ### Request body - `email` (string, required) - `first_name` (string) — Single-line name; whitespace is normalized before persistence. HTTP(S) URLs and control characters are rejected. - `last_name` (string) — Single-line name; whitespace is normalized before persistence. HTTP(S) URLs and control characters are rejected. - `instagram_handle` (string) - `tiktok_handle` (string) - `phone` (string) ### Example request ```bash curl "https://api.superfiliate.com/api/v1/customers" \ -X POST \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "email": "new@customer.com", "first_name": "John", "last_name": "Doe", "phone": "+12125551234" }' ``` ### Responses - `200` — The customer. If the email already existed, the existing customer is returned unchanged. - `401` — Unauthorized - `403` — Missing required scope - `422` — Invalid parameters Example `200` response: ```json { "id": 234456678, "email": "not-enrolled@superfiliate.com", "phone": null, "first_name": "Bob", "last_name": "Builder", "instagram_handle": null, "tiktok_handle": null, "balance": "12.3", "code": null, "reward_code": null, "microsite_url": null, "redeem_url": null, "campaign_id": null, "enrolled_at": null, "tags": [ "Potential Influencer" ] } ``` --- ## PUT /api/v1/customers/{id} — Update customer Reference: https://docs.superfiliate.com/reference/customers/update-customer Required scope: `customers.write` Updates only the supplied code, first_name, last_name, email, phone, instagram_handle, and tiktok_handle. Omitted fields stay unchanged. Social handles use the same linking behavior as Create customer: Instagram replaces the linked profile, while TikTok adds a profile to the customer's linked profiles. Names use the same first_name and last_name fields as customer creation; empty strings clear them. Email is trimmed and lowercased. Codes are uppercased and must be available, 2–30 characters long, using only A–Z, 0–9, hyphens, and underscores. Protected codes are rejected. Changing a code requires an enrolled, active customer with an existing code. It replaces the current code using the program's reward settings and removes the old code and redirects. Sending the current code again leaves it unchanged. Profile-only updates do not require enrollment. Requires customers.write. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | — | ### Request body - `code` (string) - `first_name` (string) - `last_name` (string) - `email` (string) - `instagram_handle` (string) - `tiktok_handle` (string) - `phone` (string) — Normalized to E.164. Send an empty string to clear; invalid numbers return 422. ### Example request ```bash curl "https://api.superfiliate.com/api/v1/customers/1234567890" \ -X PUT \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "code": "ALICE20", "first_name": "Alice", "last_name": "Wonder", "email": "alice@example.com", "instagram_handle": "alice", "tiktok_handle": "alice_tiktok", "phone": "+12125551234" }' ``` ### Responses - `200` — Customer updated - `401` — Unauthorized - `403` — Missing required scope - `404` — Customer not found - `422` — Invalid update Example `200` response: ```json { "id": 123345567, "email": "enrolled@superfiliate.com", "phone": "+12125551234", "first_name": "Alice", "last_name": "Wonder", "instagram_handle": "alice", "tiktok_handle": "alice_tiktok", "balance": "10.0", "code": "ALICE20", "reward_code": "CODE-TO-REDEEM-BALANCE", "microsite_url": "https://shop.com/CODE-FOR-FRIENDS", "redeem_url": "https://shop.com/discount/CODE-TO-REDEEM-BALANCE?utm_...", "campaign_id": 7890, "enrolled_at": "2024-01-01 12:00:00 UTC", "tags": [ "VIP Customer", "Newsletter Subscriber" ] } ``` --- ## POST /api/v1/customers/{id}/enroll — Enroll a customer Reference: https://docs.superfiliate.com/reference/customers/enroll-customer Required scope: `customers.write` Enrolls a customer to a campaign, if not already enrolled to a campaign. Pass `campaign_id` to enroll the customer in any campaign that belongs to the shop. If omitted, the shop's default campaign is used. If the customer is already enrolled to a campaign, it just returns the customer data. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | — | ### Request body - `code` (string) - `campaign_id` (integer) — ID of any campaign that belongs to the shop. If omitted, the shop's default campaign is used. - `code_activation` (string) — The code strategy to use. "create" will try to create a new code for the customer, using the specified code. If not available, it will append random characters until the code is available. "link" will ignore code's creation and just assume the code already exists in Shopify. "override" will try to create the specified code. If the specified code is not available, it will delete that existing code and create under the campaign settings.If this option is not specified, it will default to "create". Allowed values: `create`, `link`, `override`. ### Example request ```bash curl "https://api.superfiliate.com/api/v1/customers/1234567890/enroll" \ -X POST \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "code": "CUSTOM-CODE", "campaign_id": 123123123 }' ``` ### Responses - `200` — The enrolled customer - `401` — Unauthorized - `403` — Missing required scope - `404` — Not found Example `200` response: ```json { "id": 123345567, "email": "enrolled@superfiliate.com", "phone": "+12125551234", "first_name": "Alice", "last_name": "Wonder", "instagram_handle": "alice", "tiktok_handle": "alice_tiktok", "balance": "10.0", "code": "CODE-FOR-FRIENDS", "reward_code": "CODE-TO-REDEEM-BALANCE", "microsite_url": "https://shop.com/CODE-FOR-FRIENDS", "redeem_url": "https://shop.com/discount/CODE-TO-REDEEM-BALANCE?utm_...", "campaign_id": 7890, "enrolled_at": "2024-01-01 12:00:00 UTC", "tags": [ "VIP Customer", "Newsletter Subscriber" ] } ``` --- ## POST /api/v1/customers/{id}/add_balance — Add balance Reference: https://docs.superfiliate.com/reference/customers/add-balance Required scope: `customers.write` Adds balance to a customer. A "unique identifier" needs to be sent with it, it can be any string internal to you that we can use to guarantee that the balance is only added once. If the same unique identifier is sent twice, for the same customer, the second request will fail. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | — | ### Request body - `value` (string, required) — Between `0.01` and `99.99`. - `unique_identifier` (string, required) — To avoid duplicate rewards. ### Example request ```bash curl "https://api.superfiliate.com/api/v1/customers/1234567890/add_balance" \ -X POST \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "value": "5.55", "unique_identifier": "MyInternalRewardEvent;12345" }' ``` ### Responses - `200` — Balance added - `401` — Unauthorized - `403` — Missing required scope - `404` — Customer not found - `422` — Invalid params Example `200` response: ```json { "id": 234456678, "email": "not-enrolled@superfiliate.com", "phone": null, "first_name": "Bob", "last_name": "Builder", "instagram_handle": null, "tiktok_handle": null, "balance": "12.3", "code": null, "reward_code": null, "microsite_url": null, "redeem_url": null, "campaign_id": null, "enrolled_at": null, "tags": [ "Potential Influencer" ] } ``` --- ## GET /api/v1/customers/{id}/insights — Get customer insights Reference: https://docs.superfiliate.com/reference/customers/get-customer-insights Required scope: `customers.read` ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | — | ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `date_range` | object | no | — | ### Example request ```bash curl "https://api.superfiliate.com/api/v1/customers/1234567890/insights" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` ### Responses - `200` — Customer insights - `401` — Unauthorized - `403` — Missing required scope --- ## POST /api/v1/customers/{id}/redact — Redact a customer Reference: https://docs.superfiliate.com/reference/customers/redact-customer Required scope: `customers.write` Redacts the customer's personal data and records the redaction request for future synchronization. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | — | ### Example request ```bash curl "https://api.superfiliate.com/api/v1/customers/1234567890/redact" \ -X POST \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` ### Responses - `200` — Customer redacted - `401` — Unauthorized - `403` — Missing required scope - `404` — Customer not found - `422` — Customer could not be redacted Example `200` response: ```json { "success": true } ``` --- ## GET /api/v1/gifting_campaigns — List gifting campaigns Reference: https://docs.superfiliate.com/reference/gifting/list-gifting-campaigns Required scope: `gifting_campaigns.read` Lists seeding campaigns belonging to your shop, including campaigns without submitted gifts. These are gifting campaigns, separate from the loyalty and creator programs returned by `/api/v1/campaigns`. Use the returned `id` as `campaign_id` when listing gifting rewards. Both Shopify and merchant-managed campaigns are included; filter by `fulfillment_mode=merchant_managed` for external OMS fulfillment. The gifting rewards API only supports merchant-managed campaigns. `available_products` contains products and their eligible variants, with internal and external IDs, titles, SKUs, images, and option values. Merchant-managed campaigns use their configured variants; depleted campaign quantities and unavailable catalog entries are excluded. Shopify campaigns use their configured product and Shopify collections, excluding unavailable variants. Quantity caps and remaining quantities are campaign-wide and are null for uncapped variants. Availability is a snapshot, not a reservation; campaign and category selection limits still apply. Results include active and inactive campaigns by default and are ordered by creation time, then ID, descending. A campaign is active when neither its expiration nor archive date has passed. Pagination returns an array; request successive pages until an empty array is returned. Production requests require `gifting_campaigns.read`, which must be explicitly granted to your API key. ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | no | Only return the gifting campaign with this ID | | `active` | boolean | no | Only return active or inactive campaigns | | `fulfillment_mode` | string | no | Only return campaigns using this fulfillment mode | | `page` | integer | no | Default is `1`; values below `1` use `1` | | `items` | integer | no | Default is `25`; values are clamped between `1` and `250` | ### Example request ```bash curl "https://api.superfiliate.com/api/v1/gifting_campaigns?active=true&fulfillment_mode=merchant_managed&page=1&items=25" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` ### Responses - `200` — A list of gifting campaigns - `401` — Unauthorized - `403` — Missing required scope - `422` — Invalid filters Example `200` response: ```json [ { "id": 345678, "name": "Summer gifting", "active": true, "fulfillment_mode": "merchant_managed", "redeem_method": "manual", "multi_product_selection_limit": 2, "expires_at": null, "archived_at": null, "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z", "available_products": [ { "id": 123456, "external_id": "t-shirt", "title": "T-shirt", "variants": [ { "id": 234567, "external_id": "t-shirt-black-medium", "title": "Black / Medium", "sku": "TSHIRT-BLK-M", "image_url": null, "options": [ { "name": "Color", "value": "Black" }, { "name": "Size", "value": "Medium" } ], "available_quantity": 100, "remaining_quantity": 99 } ] } ] } ] ``` --- ## POST /api/v1/gifting_campaigns/{id}/enroll — Enroll a customer in a gifting campaign Reference: https://docs.superfiliate.com/reference/gifting/enroll-customer-in-gifting-campaign Required scope: `gifting_campaigns.write` Enrolls an existing Superfiliate customer in an active seeding/gifting campaign. Use a gifting campaign ID from `GET /api/v1/gifting_campaigns`, not a program ID from `/campaigns`. Affiliate program enrollment is not required. Shopify and merchant-managed campaigns are supported. Repeating a request returns the customer's latest existing gift in this campaign, including claimed, expired, or canceled gifts, without changing it or sending another gift. Recurrence remains managed by the campaign's existing schedule; this endpoint does not manually refresh gifts. New manual gifts are unlocked. Automatic Shopify gifts enter `ready_to_send` when an address exists, or `missing_address` otherwise. Adding a customer does not immediately place a Shopify order. Existing gifting automations run for new additions only. Requires explicitly granted `gifting_campaigns.write`; existing API keys gain no new permission. Returns 404 for a customer or campaign outside the credential's shop, and 422 for inactive campaigns. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | Superfiliate gifting campaign ID | ### Request body - `customer_id` (integer, required) — Superfiliate customer ID. ### Example request ```bash curl "https://api.superfiliate.com/api/v1/gifting_campaigns/1234567890/enroll" \ -X POST \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "customer_id": 789012 }' ``` ### Responses - `200` — The new or existing gift - `401` — Unauthorized - `403` — Missing required scope - `404` — Customer or gifting campaign not found - `422` — Invalid input or inactive campaign Example `200` response: ```json { "id": 123456, "customer_id": 789012, "gifting_campaign_id": 345678, "status": "unlocked" } ``` --- ## GET /api/v1/gifting_rewards — List gifting rewards Reference: https://docs.superfiliate.com/reference/gifting/list-gifting-rewards Required scope: `gifting_rewards.read` Lists creator details submitted for merchant-managed gifting campaigns. Use the campaign and creator IDs to create and fulfill the gifting reward in your OMS. ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `campaign_id` | integer | no | Only return gifting rewards from this campaign | | `gifting_stage` | string | no | Only return gifting rewards in this stage | | `page` | integer | no | Default is `1` | | `items` | integer | no | Default is `25` and maximum is `250` | ### Example request ```bash curl "https://api.superfiliate.com/api/v1/gifting_rewards?campaign_id=345678&gifting_stage=ready_to_send&page=1&items=25" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` ### Responses - `200` — A list of gifting rewards - `401` — Unauthorized - `403` — Missing required scope - `422` — Invalid gifting stage Example `200` response: ```json [ { "id": 123456, "gifting_stage": "ready_to_send", "campaign": { "id": 345678, "name": "Summer gifting" }, "creator": { "id": 789012, "first_name": "Alex", "last_name": "Creator", "email": "alex@example.com", "phone": "+15555550123" }, "shipping_address": { "address1": "123 Main St", "address2": "Apt 4", "city": "New York", "country_code": "US", "province_code": "NY", "zip": "10001" }, "selected_products": [ { "product_external_id": "coffee-bundle", "variant_external_id": "coffee-bundle-dark-roast", "product_title": "Coffee bundle", "variant_title": "Dark roast", "image_url": "https://example.com/dark-roast.jpg", "sku": "DARK-ROAST", "options": [ { "name": "Roast", "value": "Dark" } ], "quantity": 1, "selected_at": "2026-08-11T18:00:00Z" } ], "fulfillment": null, "details_submitted_at": "2026-08-11T18:00:00Z", "created_at": "2026-08-10T18:00:00Z", "updated_at": "2026-08-11T18:00:00Z" } ] ``` --- ## POST /api/v1/gifting_rewards/{id}/fulfill — Fulfill a gifting reward (Beta) Reference: https://docs.superfiliate.com/reference/gifting/fulfill-gifting-reward Required scope: `gifting_rewards.write` Records fulfillment for a merchant-managed gift after the creator submits their details. Use `status: order_placed` with `external_order_id` and `external_provider` to register an order before shipping. Later send `status: shipped` with tracking details. The registered order references are retained when omitted; conflicting references are rejected. Omitting `status` preserves the existing shipped behavior. Identical retries do not change timestamps or regress shipped/delivered gifts. Once shipped, different details are rejected. Campaigns using Shopify-managed fulfillment are not available through this endpoint. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | Gifting reward ID | ### Request body - `status` (string) — Allowed values: `order_placed`, `shipped`. - `external_provider` (string) — Provider name (e.g. shopify). Required for order_placed. - `external_order_id` (string) — Order ID in the merchant's OMS. - `carrier` (string) — Shipping carrier. - `tracking_number` (string) — Carrier tracking number. - `tracking_url` (string) — HTTP(S) tracking URL. ### Example request ```bash curl "https://api.superfiliate.com/api/v1/gifting_rewards/1234567890/fulfill" \ -X POST \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "status": "shipped", "external_provider": "shopify", "external_order_id": "order-123", "carrier": "UPS", "tracking_number": "1Z999AA10123456784", "tracking_url": "https://www.ups.com/track/1Z999AA10123456784" }' ``` ### Responses - `200` — Order placed before shipping - `401` — Unauthorized - `403` — Missing required scope - `404` — Gifting reward not found - `422` — Gifting reward cannot be fulfilled Example `200` response: ```json { "id": 123456, "gifting_stage": "shipped", "campaign": { "id": 345678, "name": "Summer gifting" }, "creator": { "id": 789012, "first_name": "Alex", "last_name": "Creator", "email": "alex@example.com", "phone": "+15555550123" }, "shipping_address": { "address1": "123 Main St", "address2": "Apt 4", "city": "New York", "country_code": "US", "province_code": "NY", "zip": "10001" }, "selected_products": [ { "product_external_id": "coffee-bundle", "variant_external_id": "coffee-bundle-dark-roast", "product_title": "Coffee bundle", "variant_title": "Dark roast", "image_url": "https://example.com/dark-roast.jpg", "sku": "DARK-ROAST", "options": [ { "name": "Roast", "value": "Dark" } ], "quantity": 1, "selected_at": "2026-08-11T18:00:00Z" } ], "fulfillment": { "external_order_id": "order-123", "carrier": "UPS", "tracking_number": "1Z999AA10123456784", "tracking_url": "https://www.ups.com/track/1Z999AA10123456784", "fulfilled_at": "2026-08-13T18:00:00Z" }, "details_submitted_at": "2026-08-11T18:00:00Z", "created_at": "2026-08-10T18:00:00Z", "updated_at": "2026-08-11T18:00:00Z" } ``` --- ## GET /api/v1/products — List products Reference: https://docs.superfiliate.com/reference/products/list-products Required scope: `products.read` Lists external OMS products and their variants for the authenticated shop. ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `page` | integer | no | Default is `1` | | `items` | integer | no | Default is `25` and maximum is `250` | ### Example request ```bash curl "https://api.superfiliate.com/api/v1/products?items=25&page=1" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` ### Responses - `200` — A list of products - `401` — Unauthorized - `403` — Missing required scope Example `200` response: ```json [ { "id": 123456, "source": "custom_oms", "external_id": "t-shirt", "title": "Everyday T-shirt", "url": "https://store.example/products/t-shirt", "html_description": "

Soft cotton.

", "image_url": "https://cdn.example.com/products/t-shirt.jpg", "active": true, "variants": [] } ] ``` --- ## GET /api/v1/products/{id} — Fetch product Reference: https://docs.superfiliate.com/reference/products/get-product Required scope: `products.read` Fetches one external OMS product and its variants for the authenticated shop. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | Superfiliate product ID | ### Example request ```bash curl "https://api.superfiliate.com/api/v1/products/1234567890" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` ### Responses - `200` — The product - `401` — Unauthorized - `403` — Missing required scope - `404` — Product not found --- ## POST /api/v1/products — Upsert product Reference: https://docs.superfiliate.com/reference/products/create-product Required scope: `products.write` Creates or updates an external OMS product by `source` and `external_id`. Included variants are independently upserted by their `external_id`; variants omitted from a request are left unchanged. Set `active` to `false` to disable a product or variant while retaining its history. ### Request body - `source` (string, required) — Stable source/provider key for this external catalog. - `external_id` (string, required) — Stable product identifier in the external OMS. - `title` (string, required) — Product display name. - `url` (string, required) — HTTP(S) URL for the external product. - `html_description` (string, nullable) — Optional HTML. Omit to preserve the current value; send null to clear it. - `image_url` (string, required) — HTTP(S) URL for product image. - `active` (boolean) — Defaults to `true` for a new product. - `variants` (array of object) — Variants to create or update. Omitted variants remain unchanged. - `external_id` (string, required) — Stable variant identifier. - `title` (string, required) — Variant display name. - `sku` (string, nullable) — Omit to preserve the current value; send null to clear it. - `image_url` (string, nullable) - `price` (string, nullable) — Optional decimal price in the authenticated shop's configured currency. - `active` (boolean) — Defaults to `true` for a new variant. - `options` (array of object) - `name` (string, required) - `value` (string, required) ### Example request ```bash curl "https://api.superfiliate.com/api/v1/products" \ -X POST \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "source": "custom_oms", "external_id": "everyday-t-shirt", "title": "Everyday T-shirt", "url": "https://store.example/products/t-shirt", "html_description": "

Soft cotton.

", "image_url": "https://cdn.example.com/products/t-shirt.jpg", "variants": [ { "external_id": "everyday-t-shirt-black-medium", "title": "Black / Medium", "sku": "TSHIRT-BLK-M", "price": "29.99", "options": [ { "name": "Color", "value": "Black" }, { "name": "Size", "value": "Medium" } ] } ] }' ``` ### Responses - `200` — Product upserted - `401` — Unauthorized - `403` — Missing required scope - `422` — Invalid parameters Example `200` response: ```json { "id": 123456, "source": "custom_oms", "external_id": "everyday-t-shirt", "title": "Everyday T-shirt", "url": "https://store.example/products/t-shirt", "html_description": "

Soft cotton.

", "image_url": "https://cdn.example.com/products/t-shirt.jpg", "active": true, "variants": [ { "id": 123457, "external_id": "everyday-t-shirt-black-medium", "title": "Black / Medium", "sku": "TSHIRT-BLK-M", "image_url": null, "price": "29.99", "active": true, "options": [ { "name": "Color", "value": "Black" }, { "name": "Size", "value": "Medium" } ] } ] } ``` --- ## POST /api/v1/reports — Create custom report Reference: https://docs.superfiliate.com/reference/reports/create-report Required scope: `reports.write` Creates a custom report that appears in the reporting UI. `source` selects the report type. `filters` uses the same camel-cased structure as the reporting UI. Omitted filter properties default to an unfiltered report. ### Request body - `name` (string, required) — Must be unique among the shop's custom reports. - `source` (string, required) — The dataset used by the report. Allowed values: `conversions`, `superfiliates`. - `filters` (object) - `hiddenColumnIds` (array of string) — Column identifiers hidden in the report. - `programIds` (array of string) — Program identifiers included in the report. - `superbriefIds` (array of string) — SuperBrief identifiers included in the report. Applies to conversions reports. - `creatorSources` (boolean) — Include conversions without a program or SuperBrief source. Applies to conversions reports. - `customerIds` (array of string) — Customer identifiers included in the report. - `tagIds` (array of string) — Customer tag identifiers included in the report. - `dateRange` (object) — Absolute or relative reporting period. - `startDate` (one of) - variant 1 (string) — An absolute date in YYYY-MM-DD format. - variant 2 (object) - `unit` (string) — Allowed values: `days`. - `value` (number) - variant 3 (object) - `rule` (string) — Allowed values: `week_to_date`, `month_to_date`, `quarter_to_date`, `year_to_date`. - `endDate` (one of) - variant 1 (string) — An absolute date in YYYY-MM-DD format. - variant 2 (object) - `unit` (string) — Allowed values: `days`. - `value` (number) - variant 3 (object) - `rule` (string) — Allowed values: `week_to_date`, `month_to_date`, `quarter_to_date`, `year_to_date`. - `advancedFilters` (array of object) — Column-level filters. Values for `is_in` and `is_not_in` may be arrays. - `column` (string, required) - `condition` (string, required) — Allowed values: `contains`, `does_not_contain`, `ends_with`, `greater_than`, `greater_than_or_equal_to`, `is`, `is_empty`, `is_not`, `is_not_empty`, `is_in`, `is_not_in`, `less_than`, … (+2 more). - `value` (object, nullable) — Value to compare. Omit for empty/not-empty conditions. - `columnDateRange` (object) — Absolute or relative period for one date column, such as `created_at`. - `column` (string, required) - `startDate` (one of) - variant 1 (string) — An absolute date in YYYY-MM-DD format. - variant 2 (object) - `unit` (string) — Allowed values: `days`. - `value` (number) - variant 3 (object) - `rule` (string) — Allowed values: `week_to_date`, `month_to_date`, `quarter_to_date`, `year_to_date`. - `endDate` (one of) - variant 1 (string) — An absolute date in YYYY-MM-DD format. - variant 2 (object) - `unit` (string) — Allowed values: `days`. - `value` (number) - variant 3 (object) - `rule` (string) — Allowed values: `week_to_date`, `month_to_date`, `quarter_to_date`, `year_to_date`. ### Example request ```bash curl "https://api.superfiliate.com/api/v1/reports" \ -X POST \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "name": "Monthly creator performance", "source": "superfiliates", "filters": { "programIds": [ "12345" ], "dateRange": { "startDate": { "rule": "month_to_date" }, "endDate": { "unit": "days", "value": 0 } } } }' ``` ### Responses - `200` — Custom report created - `401` — Unauthorized - `403` — Missing required scope - `422` — Invalid params Example `200` response: ```json { "id": 123123123, "name": "Monthly creator performance", "source": "superfiliates", "filters": { "hiddenColumnIds": [ "customer_phone" ], "programIds": [ "12345" ], "customerIds": [], "tagIds": [ "67890" ], "dateRange": { "startDate": { "rule": "month_to_date" }, "endDate": { "unit": "days", "value": 0 } }, "advancedFilters": [ { "column": "state", "condition": "is", "value": "Enrolled" } ], "columnDateRange": { "column": "created_at", "startDate": "2026-01-01", "endDate": "2026-01-31" } }, "is_custom": true, "created_at": "2026-01-31T12:00:00Z", "updated_at": "2026-01-31T12:00:00Z" } ``` --- ## GET /api/v1/reports — List reports Reference: https://docs.superfiliate.com/reference/reports/list-reports Required scope: `reports.read` Lists all saved reports for the authenticated shop. ### Example request ```bash curl "https://api.superfiliate.com/api/v1/reports" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` ### Responses - `200` — A list of saved reports - `401` — Unauthorized - `403` — Missing required scope Example `200` response: ```json [ { "id": 123123123, "name": "Monthly creator performance", "source": "superfiliates", "filters": { "hiddenColumnIds": [ "customer_phone" ], "programIds": [ "12345" ], "customerIds": [], "tagIds": [ "67890" ], "dateRange": { "startDate": { "rule": "month_to_date" }, "endDate": { "unit": "days", "value": 0 } }, "advancedFilters": [ { "column": "state", "condition": "is", "value": "Enrolled" } ], "columnDateRange": { "column": "created_at", "startDate": "2026-01-01", "endDate": "2026-01-31" } }, "is_custom": true, "created_at": "2026-01-31T12:00:00Z", "updated_at": "2026-01-31T12:00:00Z" } ] ``` --- ## GET /api/v1/reports/{id}/data — Fetch report data Reference: https://docs.superfiliate.com/reference/reports/fetch-report-data Required scope: `reports.read` Runs a saved report and returns its rows. Saved filters and hidden columns are applied. Superfiliate rows include `country_code` (ISO 3166-1 alpha-2), `city`, `province_code`, and `zip` when those columns are visible. Relative dates are resolved in the shop's timezone. Results are paginated with 25 rows per page by default. `has_more` and `next_page` indicate whether and where to request the next page. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | Saved report ID | ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `page` | integer | no | Default is `1` | | `items` | integer | no | Default is `25` and maximum is `250` | ### Example request ```bash curl "https://api.superfiliate.com/api/v1/reports/1234567890/data?page=1&items=25" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` ### Responses - `200` — Report data - `401` — Unauthorized - `403` — Missing required scope - `404` — Report not found - `422` — Invalid saved filters Example `200` response: ```json { "data": [ { "id": 123345567, "code": "CREATOR10", "status": "finished", "total_value": "94.5", "program_name": "Ambassador program" } ], "has_more": false, "next_page": null } ``` --- ## GET /api/v1/webhooks — List webhooks Reference: https://docs.superfiliate.com/reference/webhooks/list-webhooks Required scope: `webhooks.read` ### Example request ```bash curl "https://api.superfiliate.com/api/v1/webhooks" \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` ### Responses - `200` — A list of webhooks - `401` — Unauthorized - `403` — Missing required scope Example `200` response: ```json [ { "id": 123123123, "url": "https://yourapp.com/webhooks/superfiliate/customer_updated", "topic": "customer_updated" }, { "id": 123123123, "url": "https://yourapp.com/webhooks/superfiliate/customer_updated", "topic": "customer_updated" }, { "id": 123123123, "url": "https://yourapp.com/webhooks/superfiliate/customer_updated", "topic": "customer_updated" } ] ``` --- ## POST /api/v1/webhooks — Create webhook Reference: https://docs.superfiliate.com/reference/webhooks/create-webhook Required scope: `webhooks.write` ### Request body - `url` (string, required) - `topic` (string, required) — The event to subscribe to. Allowed values: `customer_updated`, `gifting_reward_updated`, `discount_code_created`, `discount_code_updated`, `discount_code_deleted`. ### Example request ```bash curl "https://api.superfiliate.com/api/v1/webhooks" \ -X POST \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourapp.com/webhooks/superfiliate/customer_updated", "topic": "customer_updated" }' ``` ### Responses - `200` — Webhook created - `401` — Unauthorized - `403` — Missing required scope - `422` — Invalid params Example `200` response: ```json { "id": 123123123, "url": "https://yourapp.com/webhooks/superfiliate/customer_updated", "topic": "customer_updated" } ``` --- ## DELETE /api/v1/webhooks/{id} — Remove webhook Reference: https://docs.superfiliate.com/reference/webhooks/delete-webhook Required scope: `webhooks.write` ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | integer | yes | — | ### Example request ```bash curl "https://api.superfiliate.com/api/v1/webhooks/1234567890" \ -X DELETE \ -H "Authorization: Bearer $SF_CLIENT_ID:$SF_CLIENT_SECRET" ``` ### Responses - `200` — Webhook removed - `401` — Unauthorized - `403` — Missing required scope - `404` — Webhook not found Example `200` response: ```json { "id": 123123123, "url": "https://yourapp.com/webhooks/superfiliate/customer_updated", "topic": "customer_updated" } ```