REST API

Manage Function Studio functions and promotions from your own systems with the REST API: access tokens, flow JSON, endpoints, errors and limits.

The Function Studio REST API lets your own servers manage your functions and promotions. For example, an ERP, a PIM, a CI job or an agency tool can:

  • list, read, create and change functions of every type: discounts, validations, payment customizations, delivery customizations and cart transforms;
  • list, read, create and change promotions, and read promotion analytics;
  • activate, deactivate and schedule functions and promotions;
  • validate a flow, and test a function against a sample cart, with no save.

You send the logic of a function as flow JSON. This is the same format as the file that Export logic makes in the app. You send the other settings (title, discount code, combinations and so on) as separate JSON fields.

The REST API is available on the Advanced plan and higher. If a store moves to a lower plan, its tokens stay, but requests get 403 plan_required until it upgrades again.

Quick start

  1. In Function Studio, go to Settings > REST API > Manage access tokens.
  2. Click Create token. Enter a name, select the scopes, and select an expiry.
  3. Copy the token. Function Studio shows the token one time only. Keep it in a secret store. A new token can take up to one minute to work everywhere.
  4. Send a request:
curl -H "Authorization: Bearer fs_live_…" \
  https://rest.functions-studio.easylogictools.com/v1/me

The response shows the store, the token name and its scopes.

Base URL and specification

Base URLhttps://rest.functions-studio.easylogictools.com/v1
OpenAPI 3.1 specificationhttps://rest.functions-studio.easylogictools.com/v1/openapi.json

Import the OpenAPI file into Postman, Insomnia or a code generator to get every endpoint and schema.

Access tokens

  • A token looks like fs_live_0123456789ab_…. The first part after fs_live_ is the token id. The settings page and the request log show it.
  • Send the token in the Authorization header only: Authorization: Bearer <token>. Function Studio refuses a token in the URL.
  • The REST API is for servers. Do not use a token in a browser or in a theme. Function Studio refuses requests with an Origin header.
  • A store can have up to 10 tokens.
  • Expiry: 30 days, 90 days, 365 days or never.
  • IP allow list (optional): up to 10 IPv4/IPv6 addresses or CIDR ranges, for example 203.0.113.7 or 198.51.100.0/24. Requests from other addresses get 403 ip_not_allowed.
  • Revoke a token on the same settings page. The revoke has an immediate effect.
  • Function Studio keeps only a hash of the token. If you lose a token, revoke it and create a new one.

Scopes

Select only the scopes that your integration needs. A write or activate scope also gives the read scope of the same group.

ScopeAllows
functions:readList, read, validate and test functions. Search the store catalog
functions:writeCreate and change functions. New functions are always inactive
functions:activateActivate, deactivate and schedule functions. Also necessary to change a live function
promotions:readList, read and validate promotions
promotions:writeCreate and change promotions. New promotions are always inactive
promotions:activateActivate and deactivate promotions. Also necessary to change a live promotion
analytics:readRead promotion analytics

Flow JSON

A flow is an object with two arrays:

{
  "nodes": [{ "id": "…", "type": "…", "data": {}, "position": { "x": 0, "y": 0 } }],
  "edges": [{ "source": "…", "target": "…", "sourceHandle": "…" }]
}

The easiest way to get a correct flow:

  1. Build the function in the visual editor.
  2. Click Export logic. You get a .json file with the flow.
  3. Change the values in the file (for example a percentage or a product id) in your system, and send the file as flow.

Function Studio checks every flow before it saves it. It does the same checks as the editor: block types, connections, conditions, the function input that the flow needs, your plan, and the compiled size. If a check fails, the response is 400 invalid_input with a list of diagnostics. Each diagnostic names the location, for example nodes[3].data.value.

Limits: 500 nodes, 1,000 edges, 1 MB for the request body.

To find ids of products, collections, variants and metafield definitions for a flow, use GET /v1/store/search.

Functions

List

curl -H "Authorization: Bearer $TOKEN" \
  "https://rest.functions-studio.easylogictools.com/v1/functions?type=discount&status=active&limit=25"

Filters: type (discount, validation, paymentCustomization, deliveryCustomization, cartTransform), status (active, inactive, scheduled, expired), limit (1–100), cursor. If there are more items, the response has nextCursor. Send it as cursor to get the next page.

A function id looks like discount:automatic:1234567890. Use the id exactly as the API returns it.

Read

curl -H "Authorization: Bearer $TOKEN" \
  https://rest.functions-studio.easylogictools.com/v1/functions/discount:automatic:1234567890

The response has the title, the status, the dates, the settings and the flow.

Create

curl -X POST https://rest.functions-studio.easylogictools.com/v1/functions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: erp-import-2026-10-22-001" \
  -d @- <<'JSON'
{
  "type": "discount",
  "title": "VIP 10% off",
  "method": "automatic",
  "flow": { "nodes": [ … ], "edges": [ … ] },
  "settings": {
    "combinesWith": { "orderDiscounts": false, "productDiscounts": true, "shippingDiscounts": true },
    "usageLimit": null
  }
}
JSON
FieldRequiredDescription
typeyesFunction type
titleyesTitle. For a code discount, the code is the title
flowyesFlow JSON
methodnoDiscounts only: automatic (default) or code
codefor method: "code"Discount code
settingsnoDiscounts: combinesWith, usageLimit, appliesOncePerCustomer, appliesOnOneTimePurchase, appliesOnSubscription, recurringCycleLimit, tags. Validations: blockOnFailure

The new function is always inactive. The response is 201 with the function.

Change

PATCH /v1/functions/{id} with one or more of title, flow, settings. The status does not change.

  • If the function is live (active or scheduled), the token also needs functions:activate.
  • A function cannot move to another function input. If the new flow needs another input (for example customer metafields), the response is 400 and tells you to use recreate.

Recreate

POST /v1/functions/{id}/recreate with flow (and optional title) makes a new inactive function on the input that the flow needs. It copies the settings of the old function, and then deletes the old function. The id changes. The response has replacedId and wasActive. Use it only when a change needs another input.

Activate and deactivate

curl -X POST https://rest.functions-studio.easylogictools.com/v1/functions/discount:automatic:1234567890/status \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"status":"active"}'

Schedule (discounts)

PUT /v1/functions/{id}/schedule with startsAt and/or endsAt (ISO 8601, for example 2026-11-27T00:00:00Z). "endsAt": null removes the end date.

Validate and test

  • POST /v1/functions/validate with type and flow (and optional id of a saved function) checks a flow. Nothing is saved.
  • POST /v1/functions/test runs a saved function (id) or a flow (type + flow) against a sample cart. Nothing is saved.
{
  "id": "discount:automatic:1234567890",
  "cart": {
    "lines": [{ "product": "Classic Hoodie", "quantity": 2 }],
    "customer": { "tags": ["VIP"] },
    "discount_codes": [],
    "shipping": { "country_code": "US" }
  }
}

The response shows the discounts per line, order and shipping, validation errors, hidden or renamed payment and delivery options, cart transform operations, and the function logs.

Promotions

MethodPathScope
GET/v1/promotionspromotions:read
POST/v1/promotionspromotions:write
GET/v1/promotions/{id}promotions:read
PATCH/v1/promotions/{id}promotions:write
POST/v1/promotions/{id}/statuspromotions:activate
POST/v1/promotions/validatepromotions:read
GET/v1/promotions/{id}/analytics?period=7danalytics:read

Create body: name, flow, and optional autoCreateDiscount (default true: the promotion makes its discount when you activate it). New promotions are always inactive. Status body: { "isActive": true }. Analytics periods: 1h, 6h, 24h, 7d (default), 30d.

All endpoints

MethodPathScope
GET/v1/meany
GET/v1/functionsfunctions:read
POST/v1/functionsfunctions:write
POST/v1/functions/validatefunctions:read
POST/v1/functions/testfunctions:read
GET/v1/functions/{id}functions:read
PATCH/v1/functions/{id}functions:write
POST/v1/functions/{id}/recreatefunctions:write
POST/v1/functions/{id}/statusfunctions:activate
PUT/v1/functions/{id}/schedulefunctions:activate
GET/v1/promotions …see above
GET/v1/store/search?kind=product&query=hoodiefunctions:read
GET/v1/openapi.jsonnone

Add ?include=dsl to a read to also get the program as Python DSL (the format that the AI assistant and the MCP connector use).

Errors

Every error has the same shape:

{
  "error": {
    "code": "invalid_input",
    "message": "The flow has errors.",
    "requestId": "8c1f…",
    "details": { "diagnostics": [{ "severity": "error", "message": "nodes[2]: …" }] }
  }
}
HTTPcodeMeaning
400invalid_input, invalid_jsonThe request is not valid. See details
401missing_token, invalid_token, token_expiredSend a valid token
403insufficient_scopeThe token needs more scopes. details.requiredScopes lists them
403ip_not_allowedThe address is not in the IP allow list of the token
403plan_required, activation_gated, forbiddenPlan or store limit
404not_foundUnknown id or route
409idempotency_conflictThe Idempotency-Key was used with another request
409idempotency_in_progressA request with this Idempotency-Key is still running
413payload_too_largeThe body is larger than 1 MB
415unsupported_media_typeSend Content-Type: application/json
429rate_limitedToo many requests. Wait for Retry-After seconds
502upstreamShopify did not accept or answer the request. Try again

Send the requestId (also in the X-Request-Id header) to support when you ask for help.

Limits

LimitValue
Requests per token60 per minute
Changes per token (create, change, status, schedule, test)20 per minute
Creates per store (REST API and AI clients together)100 per day (UTC)
Activations and schedules per store (REST API and AI clients together)50 per day (UTC)
Request body1 MB
Flow500 nodes, 1,000 edges

Safe retries

Send an Idempotency-Key header (1–255 characters, for example a UUID) with POST /v1/functions, POST /v1/functions/{id}/recreate and POST /v1/promotions.

  • If you send the same key with the same request (the same URL and the same body bytes) again within 24 hours, you get the first response again, with the header Idempotent-Replayed: true. Function Studio does not create a second function, and the retry does not count against the daily limit.
  • If the first request is still running, the retry gets 409 idempotency_in_progress. Wait for Retry-After seconds and send it again.
  • If the first request failed, a retry with the same key runs again.
  • The same key with a different request gets 409 idempotency_conflict.

Request log

Settings > REST API > Manage access tokens shows the 50 newest requests: time, token, route, HTTP status and duration. It also shows requests that a token’s IP allow list refused. Requests refused by a rate limit (429) are not in the log. Function Studio does not keep the request bodies.

Security checklist

  • Keep tokens in a secret store, never in code, logs or a browser.
  • Give each integration its own token with the smallest scopes.
  • Set an expiry and an IP allow list when you can.
  • Revoke a token at once if it leaks, or when a person who knew it leaves.
  • If you uninstall Function Studio, all tokens are revoked.
  • The request log in the settings shows every request of every token. On the Advanced plan, the function history also shows changes through the REST API, with the token id.