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_requireduntil it upgrades again.
Quick start
- In Function Studio, go to Settings > REST API > Manage access tokens.
- Click Create token. Enter a name, select the scopes, and select an expiry.
- 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.
- 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 URL | https://rest.functions-studio.easylogictools.com/v1 |
| OpenAPI 3.1 specification | https://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 afterfs_live_is the token id. The settings page and the request log show it. - Send the token in the
Authorizationheader 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
Originheader. - 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.7or198.51.100.0/24. Requests from other addresses get403 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.
| Scope | Allows |
|---|---|
functions:read | List, read, validate and test functions. Search the store catalog |
functions:write | Create and change functions. New functions are always inactive |
functions:activate | Activate, deactivate and schedule functions. Also necessary to change a live function |
promotions:read | List, read and validate promotions |
promotions:write | Create and change promotions. New promotions are always inactive |
promotions:activate | Activate and deactivate promotions. Also necessary to change a live promotion |
analytics:read | Read 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:
- Build the function in the visual editor.
- Click Export logic. You get a
.jsonfile with the flow. - 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
| Field | Required | Description |
|---|---|---|
type | yes | Function type |
title | yes | Title. For a code discount, the code is the title |
flow | yes | Flow JSON |
method | no | Discounts only: automatic (default) or code |
code | for method: "code" | Discount code |
settings | no | Discounts: 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
400and 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/validatewithtypeandflow(and optionalidof a saved function) checks a flow. Nothing is saved.POST /v1/functions/testruns a saved function (id) or a flow (type+flow) against a samplecart. 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
| Method | Path | Scope |
|---|---|---|
| GET | /v1/promotions | promotions:read |
| POST | /v1/promotions | promotions:write |
| GET | /v1/promotions/{id} | promotions:read |
| PATCH | /v1/promotions/{id} | promotions:write |
| POST | /v1/promotions/{id}/status | promotions:activate |
| POST | /v1/promotions/validate | promotions:read |
| GET | /v1/promotions/{id}/analytics?period=7d | analytics: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
| Method | Path | Scope |
|---|---|---|
| GET | /v1/me | any |
| GET | /v1/functions | functions:read |
| POST | /v1/functions | functions:write |
| POST | /v1/functions/validate | functions:read |
| POST | /v1/functions/test | functions:read |
| GET | /v1/functions/{id} | functions:read |
| PATCH | /v1/functions/{id} | functions:write |
| POST | /v1/functions/{id}/recreate | functions:write |
| POST | /v1/functions/{id}/status | functions:activate |
| PUT | /v1/functions/{id}/schedule | functions:activate |
| GET | /v1/promotions … | see above |
| GET | /v1/store/search?kind=product&query=hoodie | functions:read |
| GET | /v1/openapi.json | none |
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]: …" }] }
}
}
| HTTP | code | Meaning |
|---|---|---|
| 400 | invalid_input, invalid_json | The request is not valid. See details |
| 401 | missing_token, invalid_token, token_expired | Send a valid token |
| 403 | insufficient_scope | The token needs more scopes. details.requiredScopes lists them |
| 403 | ip_not_allowed | The address is not in the IP allow list of the token |
| 403 | plan_required, activation_gated, forbidden | Plan or store limit |
| 404 | not_found | Unknown id or route |
| 409 | idempotency_conflict | The Idempotency-Key was used with another request |
| 409 | idempotency_in_progress | A request with this Idempotency-Key is still running |
| 413 | payload_too_large | The body is larger than 1 MB |
| 415 | unsupported_media_type | Send Content-Type: application/json |
| 429 | rate_limited | Too many requests. Wait for Retry-After seconds |
| 502 | upstream | Shopify 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
| Limit | Value |
|---|---|
| Requests per token | 60 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 body | 1 MB |
| Flow | 500 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 forRetry-Afterseconds 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.