FlowStock API v2

Base URL: https://dev2025.flowstock.pro/api/v2 · 300 req/min · default page size 20 (max 100)

Getting Started

FlowStock's REST API (v2) lets you read and write products, purchase orders, warehouse stock, tasks and users for a single shop.

This page is documentation only. It is served from a different host than the API itself. Every request you actually make goes to the base URL below — not to the domain this page is on.

Base URL: https://dev2025.flowstock.pro/api/v2

All endpoints are versioned under /api/v2. There is no self-service signup: a FlowStock admin creates an API user for your shop from the admin panel (Shop → API Users), which is where you get your X-Api-Key / X-Api-Email pair and choose which scopes it can use.

Every response carries an X-Api-Version: 2 header and echoes back an X-Request-Id header (send your own via X-Request-Id to correlate a request with your own logs, or let the API generate one for you).

Responses are always JSON. A successful response wraps its payload in a top-level data key; list endpoints add a meta key with pagination info. Errors always look like:

{ "error": { "code": "NOT_FOUND", "message": "Product not found." } }

See Errors for the full list of error codes.

Authentication

Every request must include two headers:

X-Api-Key: fs_<random>
X-Api-Email: you@example.com

The key always starts with fs_. Both headers are required on every request — there is no session, cookie, or bearer-token alternative for this API.

A locked-out or invalid attempt never reveals which part was wrong (whether the email exists, or the key format vs. the key value) — you'll get one of the codes below either way.

Code Status Meaning
MISSING_CREDENTIALS 401 One or both headers were not sent.
INVALID_CREDENTIALS 401 The key or email did not match an active API user.
API_KEY_EXPIRED 401 The key's expiry date has passed.
TOO_MANY_FAILED_ATTEMPTS 429 5 failed attempts for this email+IP within the lockout window — wait before retrying, even with correct credentials.

Scopes

Each API user is granted one or more scopes by the admin who creates it. A request missing the scope its endpoint requires gets 403 INSUFFICIENT_SCOPE. One endpoint, GET /products/{productCode}/warehouses, requires two scopes at once (products.read + warehouses.read) — every other endpoint needs only the single scope listed next to it below.

Scope Grants
products.read Read products, variations, barcodes, stock events.
products.write Update backorder status, manage barcodes, trigger a catalog resync.
purchase_orders.read List and view purchase orders.
purchase_orders.write Create/update purchase orders and their product lines, import, change status.
warehouses.read List warehouses and read warehouse stock.
tasks.read List and view tasks.
tasks.write Create and update tasks.
users.read Look up users of this shop, for task assignment.

Rate Limiting

Requests are rate-limited per API user (not per IP), so multiple integrations sharing an office network never interfere with each other. Going over the limit returns 429 RATE_LIMIT_EXCEEDED.

Idempotency is required on every POST — see the Idempotency section.

Limit Applies to
300 requests / minute Every authenticated request.

Idempotency

Every POST request must include an X-Idempotency-Key header (1–255 characters, [a-zA-Z0-9\-_:.]). GET, PATCH and DELETE don't need one — they're treated as inherently safe to retry.

  • Missing key → 400 MISSING_IDEMPOTENCY_KEY.
  • Malformed key → 400 INVALID_IDEMPOTENCY_KEY.
  • Reusing a key that already produced a response → that exact response (including its status code) is replayed, with an X-Idempotent-Replayed: true header added. This applies to error responses too, not just successes.
  • A different key, but the same path + body submitted again within 10 seconds → 409 DUPLICATE_REQUEST.
Reusing the same key with a genuinely different body currently replays the first response rather than rejecting the request. If your integration retries with a modified payload, use a fresh key.

Errors

Errors always take this shape, with an optional details key for field-level validation messages:

{ "error": { "code": "VALIDATION_ERROR", "message": "The given data was invalid.", "details": { "quantity": ["The quantity field is required."] } } }

A 404 NOT_FOUND is also returned for a record that exists but belongs to a different shop — the API never returns 403 for that case, so a caller can't use the response to probe whether an ID exists elsewhere.

Code Status Where it happens
HTTPS_REQUIRED 403 Request made over plain HTTP in production.
MISSING_CREDENTIALS 401 X-Api-Key or X-Api-Email header missing.
INVALID_CREDENTIALS 401 Key/email do not match an active API user.
API_KEY_EXPIRED 401 The API user's key has expired.
TOO_MANY_FAILED_ATTEMPTS 429 Brute-force lockout window active.
RATE_LIMIT_EXCEEDED 429 Over 300 requests/minute for this API user.
INSUFFICIENT_SCOPE 403 The API user's scopes don't include this endpoint's required scope.
MISSING_IDEMPOTENCY_KEY 400 POST request sent without X-Idempotency-Key.
INVALID_IDEMPOTENCY_KEY 400 X-Idempotency-Key does not match the allowed format.
DUPLICATE_REQUEST 409 Same path + body resubmitted under a different key within 10s.
VALIDATION_ERROR 422 Request body/query failed validation (see details).
NOT_FOUND 404 Resource doesn't exist, or belongs to a different shop.
ORDER_IMMUTABLE 422 Write attempted on a purchase order already imported.
SUPPLIER_NOT_FOUND 422 supplier_id doesn't resolve to a supplier of this shop.
WAREHOUSE_NOT_FOUND 422 warehouse_id doesn't resolve to a warehouse of this shop.
SUPPLIER_PRODUCT_NOT_FOUND 422 No SupplierProduct links this product to the resolved supplier.
PRODUCTS_UNRESOLVED 422 A product_code in the request couldn't be resolved (only_existing=false).
INVALID_ASSIGNEE 422 assigned_to isn't a user of this shop.
INVALID_STATUS_TRANSITION 422 Requested status change is not an allowed transition.
INVALID_STATUS_FOR_IMPORT 422 Purchase order is not in an importable status.
MCF_SYNC_FAILED 502 The upstream MCF push failed; no local change was made.
METHOD_NOT_ALLOWED 405 Wrong HTTP verb for this path.
UNAUTHENTICATED 401 Generic auth failure outside the normal credential-check path.
INTERNAL_ERROR 500 Uncaught server error.

Products

{productCode} resolves to either a product or a variation — both share the same product-code namespace per shop. Every endpoint below returns 404 NOT_FOUND for an unknown code, or one belonging to a different shop.

GET /products/{productCode}

Resolve a product or variation by code

Scope: products.read

price / purchase_price fall back to the parent product's value whenever a variation's own value is empty.

Param Type Rules Description
productCode string path The product or variation code.
Request
curl "https://dev2025.flowstock.pro/api/v2/products/SKU-001" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com"
Response
{
  "data": {
    "product_code": "SKU-001",
    "product_id": 42,
    "variation_id": null,
    "name": "Blue Widget",
    "product_name": "Blue Widget",
    "variation_name": null,
    "price": "19.90",
    "purchase_price": "8.50",
    "supplier_id": 3,
    "stock": {
      "quantity": 100,
      "reserved": 5,
      "balance": 95,
      "location": "A-12",
      "backorder_enabled": false,
      "backorder_estimate": null
    }
  }
}
PATCH /products/{productCode}/backorder

Update backorder status

Scope: products.write

Pushes the change to MCF first — only persists locally on success. An omitted field keeps its current value; it is not reset. Sending estimate without enabled forces enabled to true.

Param Type Rules Description
enabled boolean sometimes Whether backorders are allowed.
estimate string|null sometimes, max:255 Free-text delivery estimate shown to customers.

MCF failure → 502 MCF_SYNC_FAILED, and no local write happens.

Request
curl -X PATCH "https://dev2025.flowstock.pro/api/v2/products/SKU-001/backorder" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true, "estimate": "Ships in 2 weeks"}'
Response
{
  "data": {
    "product_code": "SKU-001",
    "backorder_enabled": true,
    "backorder_estimate": "Ships in 2 weeks"
  }
}
GET /products/{productCode}/barcodes

List barcodes

Scope: products.read

Request
curl "https://dev2025.flowstock.pro/api/v2/products/SKU-001/barcodes" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com"
Response
{
  "data": [
    { "id": 1, "value": "0123456789012", "product_id": 42, "variation_id": null }
  ]
}
POST /products/{productCode}/barcodes

Add a barcode

Scope: products.write

Param Type Rules Description
value string required, max:255 The barcode value.
Request
curl -X POST "https://dev2025.flowstock.pro/api/v2/products/SKU-001/barcodes" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com" \
  -H "X-Idempotency-Key: add-barcode-001" \
  -H "Content-Type: application/json" \
  -d '{"value": "0123456789012"}'
Response
{
  "data": {
    "id": 1,
    "value": "0123456789012",
    "product_id": 42,
    "variation_id": null
  }
}
DELETE /products/{productCode}/barcodes/{barcodeId}

Remove a barcode

Scope: products.write

Param Type Rules Description
barcodeId integer path Must belong to this product's/variation's own barcode list.
Request
curl -X DELETE "https://dev2025.flowstock.pro/api/v2/products/SKU-001/barcodes/1" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com"
Response
204 No Content
POST /products/sync

Trigger a full catalog resync from MCF

Scope: products.write

No body. Always shop-wide — there is no per-product resync. Dispatches the same jobs as the admin panel's "reload shop products" action, asynchronously.

Request
curl -X POST "https://dev2025.flowstock.pro/api/v2/products/sync" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com" \
  -H "X-Idempotency-Key: sync-2026-08-12-01"
Response
{
  "data": {
    "status": "queued"
  }
}
POST /products/{productCode}/purchase-orders

Add this product to a purchase order

Scopes: products.read purchase_orders.write

Pass exactly one of purchase_order_id (adds a line to an existing, open order) or supplier_id (creates a new draft order for that supplier first, then adds the line).

Param Type Rules Description
purchase_order_id integer|null prohibits:supplier_id Existing order to add a line to.
supplier_id integer|null required_without:purchase_order_id Creates a new draft order for this supplier.
quantity integer required, min:1 Line quantity.

422 SUPPLIER_PRODUCT_NOT_FOUND if no supplier-product link exists; 422 ORDER_IMMUTABLE if the target order is already imported.

Request
curl -X POST "https://dev2025.flowstock.pro/api/v2/products/SKU-001/purchase-orders" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com" \
  -H "X-Idempotency-Key: add-to-po-001" \
  -H "Content-Type: application/json" \
  -d '{"supplier_id": 3, "quantity": 50}'
Response
{
  "data": {
    "id": 501,
    "purchase_order_id": "2026072815",
    "purchase_product_id": 9001
  }
}
GET /products/{productCode}/stock-events

Paginated stock event log

Scope: products.read

Param Type Rules Description
type string|null max:100 Filter by source_type.
page integer default 1
per_page integer default 20, max 100
Request
curl "https://dev2025.flowstock.pro/api/v2/products/SKU-001/stock-events?page=1&per_page=20" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com"
Response
{
  "data": [
    { "id": 1, "source_type": "order", "source_id": 55, "additional_info": null, "quantity": 100, "quantity_change": -2, "changed_at": "2026-07-30T10:00:00+00:00" }
  ],
  "meta": { "total": 40, "page": 1, "per_page": 20 }
}
GET /products/{productCode}/warehouses

Stock for this product across every warehouse

Scopes: products.read warehouses.read

One row per warehouse this product has a stock row in. To look up the full stock list of one already-identified warehouse instead, see GET /warehouses/stock.

Param Type Rules Description
page integer default 1
per_page integer default 20, max 100
Request
curl "https://dev2025.flowstock.pro/api/v2/products/SKU-001/warehouses" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com"
Response
{
  "data": [
    { "warehouse_id": 1, "warehouse_name": "Main Warehouse", "quantity": 100, "min_stock": 10, "max_stock": 200, "location": "A-12", "status": "in-stock" },
    { "warehouse_id": 2, "warehouse_name": "Berlin Store", "quantity": 15, "min_stock": 5, "max_stock": 50, "location": "B-03", "status": "low-in-stock" }
  ],
  "meta": { "total": 2, "page": 1, "per_page": 20 }
}

Purchase Orders

{id} is always the numeric primary key. Any write on an order already imported returns 422 ORDER_IMMUTABLE, checked before any other validation. Status is always sent/received as a lowercase string: draft, sent, completed, imported, partially_imported.

Status transitions: draft/sent/ completed/partially_imported can move freely between each other. imported is reachable only from completed or partially_imported, and once imported no further transition is allowed.

GET /purchase-orders

List, filtered/paginated

Scope: purchase_orders.read

Param Type Rules Description
status string One of the status values above.
supplier_id integer
date_from / date_to date
search string max:255
page / per_page integer per_page max 100
Request
curl "https://dev2025.flowstock.pro/api/v2/purchase-orders?status=sent&per_page=20" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com"
Response
{
  "data": [ { "...": "purchase order" } ],
  "meta": { "total": 12, "page": 1, "per_page": 20 }
}
GET /purchase-orders/{id}

Fetch one, with its product lines

Scope: purchase_orders.read

Request
curl "https://dev2025.flowstock.pro/api/v2/purchase-orders/501" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com"
Response
{
  "data": {
    "id": 501,
    "purchase_order_id": "2026072815",
    "status": "sent",
    "supplier_id": 3,
    "warehouse_id": 1,
    "arrival_date": "2026-08-05",
    "reference_number": "PO-2026-001",
    "comment": null,
    "checked": false,
    "overdue": false,
    "currency": "EUR",
    "imported_at": null,
    "products": [
      {
        "id": 9001,
        "product_id": 42,
        "product_code": "SKU-001",
        "name": "Blue Widget",
        "quantity": 10,
        "import_quantity": 0,
        "unit": "pcs",
        "purchase_price": "8.50",
        "checked": false
      }
    ]
  }
}
POST /purchase-orders

Create, optionally with inline product lines

Scope: purchase_orders.write

Product resolution happens before the order is created. With only_existing=false (default), any unresolvable line aborts the whole request — nothing is persisted. With only_existing=true, unresolvable lines are skipped and reported in meta.skipped_product_codes; the order is still created.

Param Type Rules Description
supplier_id integer required
warehouse_id integer|null
arrival_date date|null
reference_number string|null max:255
only_existing boolean default false
products[].product_code / products[].quantity string / integer required with products, quantity min:1 Inline lines to create with the order.

422 PRODUCTS_UNRESOLVED, 422 SUPPLIER_NOT_FOUND, 422 WAREHOUSE_NOT_FOUND as described above.

Request
curl -X POST "https://dev2025.flowstock.pro/api/v2/purchase-orders" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com" \
  -H "X-Idempotency-Key: create-po-001" \
  -H "Content-Type: application/json" \
  -d '{"supplier_id": 3, "warehouse_id": 1, "products": [{"product_code": "SKU-001", "quantity": 10}]}'
Response
{
  "data": {
    "id": 501,
    "purchase_order_id": "2026072815",
    "status": "draft"
  }
}
PATCH /purchase-orders/{id}

Update order-level fields

Scope: purchase_orders.write

All fields are optional; an omitted field keeps its current value. Because of that, warehouse_id can't be cleared back to null this way — only reassigned to a different warehouse.

Param Type Rules Description
supplier_id / warehouse_id integer sometimes
arrival_date date sometimes
reference_number string sometimes, max:255
comment string sometimes
Request
curl -X PATCH "https://dev2025.flowstock.pro/api/v2/purchase-orders/501" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com" \
  -H "Content-Type: application/json" \
  -d '{"reference_number": "PO-2026-001-REV2"}'
Response
{
  "data": {
    "id": 501,
    "reference_number": "PO-2026-001-REV2"
  }
}
POST /purchase-orders/{id}/products

Add a product line

Scope: purchase_orders.write

Param Type Rules Description
product_code string required, max:255
quantity integer required, min:1

422 SUPPLIER_PRODUCT_NOT_FOUND if no SupplierProduct links the code to this order's supplier.

Request
curl -X POST "https://dev2025.flowstock.pro/api/v2/purchase-orders/501/products" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com" \
  -H "X-Idempotency-Key: add-line-001" \
  -H "Content-Type: application/json" \
  -d '{"product_code": "SKU-002", "quantity": 5}'
Response
{
  "data": {
    "id": 9002,
    "product_code": "SKU-002",
    "quantity": 5,
    "checked": false
  }
}
PATCH /purchase-orders/{id}/products/{productId}

Update a product line

Scope: purchase_orders.write

All fields optional, omitted = unchanged. A quantity change adds a "Change amount" log entry on the order.

Param Type Rules Description
quantity integer sometimes, min:1
purchase_price string sometimes, max:255
unit string sometimes, max:255
additional_info string|null sometimes
Request
curl -X PATCH "https://dev2025.flowstock.pro/api/v2/purchase-orders/501/products/9002" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com" \
  -H "Content-Type: application/json" \
  -d '{"quantity": 8}'
Response
{
  "data": {
    "id": 9002,
    "quantity": 8
  }
}
DELETE /purchase-orders/{id}/products/{productId}

Soft-delete a product line

Scope: purchase_orders.write

Request
curl -X DELETE "https://dev2025.flowstock.pro/api/v2/purchase-orders/501/products/9002" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com"
Response
204 No Content
PATCH /purchase-orders/{id}/products/{productId}/confirm

Flip a line's checked flag

Scope: purchase_orders.write

When every line on the order is checked, the order's own checked flag flips too.

Param Type Rules Description
checked boolean required
Request
curl -X PATCH "https://dev2025.flowstock.pro/api/v2/purchase-orders/501/products/9002/confirm" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com" \
  -H "Content-Type: application/json" \
  -d '{"checked": true}'
Response
{
  "data": {
    "id": 9002,
    "checked": true
  }
}
POST /purchase-orders/{id}/import

Import checked lines into stock

Scope: purchase_orders.write

No body. Imports every line that is checked and not yet imported. If nothing qualifies, this is a no-op: 200 with an empty list and the status left untouched.

422 INVALID_STATUS_FOR_IMPORT if the order is not in an importable status.

Request
curl -X POST "https://dev2025.flowstock.pro/api/v2/purchase-orders/501/import" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com" \
  -H "X-Idempotency-Key: import-po-501"
Response
{
  "data": {
    "imported_product_codes": ["SKU-001", "SKU-002"],
    "status": "partially_imported"
  }
}
PATCH /purchase-orders/{id}/status

Change order status

Scope: purchase_orders.write

Param Type Rules Description
status string required One of the status values above.

422 INVALID_STATUS_TRANSITION for a disallowed change; 422 ORDER_IMMUTABLE if already imported.

Request
curl -X PATCH "https://dev2025.flowstock.pro/api/v2/purchase-orders/501/status" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com" \
  -H "Content-Type: application/json" \
  -d '{"status": "completed"}'
Response
{
  "data": {
    "id": 501,
    "status": "completed"
  }
}

Warehouses

Read-only stock lookups — warehouses themselves are managed in the admin panel, so there's no warehouse CRUD here.

GET /warehouses

List warehouses, filtered/paginated

Scope: warehouses.read

Param Type Rules Description
search string max:255 Substring match on name/city, exact match on warehouse_code.
active boolean default true Pass active=false explicitly to see inactive warehouses.
page / per_page integer
Request
curl "https://dev2025.flowstock.pro/api/v2/warehouses?active=true" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com"
Response
{
  "data": [
    { "id": 1, "name": "Main Warehouse", "warehouse_code": "WH-MAIN", "city": "Berlin", "active": true, "main": true, "pos_allow_sales": true, "stock_status": "in-stock" }
  ],
  "meta": { "total": 6, "page": 1, "per_page": 20 }
}
GET /warehouses/stock

Full stock list for one warehouse

Scope: warehouses.read

Exactly one of warehouse_id/warehouse_code must be given. To ask "where is product Y stocked across every warehouse" instead, use GET /products/{productCode}/warehouses.

Param Type Rules Description
warehouse_id integer required_without:warehouse_code Must belong to the authenticated shop.
warehouse_code string max:255, required_without:warehouse_id Exact match; must belong to the authenticated shop.
sort string one of product_code, name, quantity, updated_at Default product_code.
sort_dir string asc|desc Default asc.
page / per_page integer

status is one of not-tracked, low-in-stock, in-stock, out-of-stock, delivery.

Request
curl "https://dev2025.flowstock.pro/api/v2/warehouses/stock?warehouse_id=1&sort=quantity&sort_dir=desc" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com"
Response
{
  "data": [
    {
      "warehouse_id": 1,
      "warehouse_name": "Main Warehouse",
      "product_id": 42,
      "product_code": "SKU-001",
      "name": "Blue Widget",
      "quantity": 100,
      "min_stock": 10,
      "max_stock": 200,
      "location": "A-12",
      "status": "in-stock"
    }
  ],
  "meta": { "total": 340, "page": 1, "per_page": 20 }
}

Tasks

Full CRUD except delete, for FlowStock's internal to-do/CRM-event task. {id} is the numeric primary key.

Status: new, in_progress, completed — not settable on create, a task always starts new.
Priority: low, normal, high, urgent, immediate.
Type: order, outbound_call, inbound_call, registration, note, offer, meeting.

GET /tasks

List, filtered/paginated

Scope: tasks.read

Param Type Rules Description
status / priority / type string one of the slugs above
assigned_to integer FlowStock user ID.
due_date_from / due_date_to date
search string max:255 Substring match on name.
page / per_page integer
Request
curl "https://dev2025.flowstock.pro/api/v2/tasks?status=new&priority=urgent" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com"
Response
{
  "data": [ { "...": "task" } ],
  "meta": { "total": 8, "page": 1, "per_page": 20 }
}
GET /tasks/{id}

Fetch one

Scope: tasks.read

Request
curl "https://dev2025.flowstock.pro/api/v2/tasks/501" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com"
Response
{
  "data": {
    "id": 501,
    "name": "Call the supplier",
    "description": "Ask about the delayed shipment",
    "status": "new",
    "priority": "urgent",
    "type": "outbound_call",
    "due_date": "2026-09-01",
    "assigned_to": 7
  }
}
POST /tasks

Create

Scope: tasks.write

Call GET /users first to find a valid assigned_to value.

Param Type Rules Description
name string required, max:255
description string|null
type / priority string required, one of the slugs above
due_date date|null
assigned_to integer required Must be a user of this shop.

422 INVALID_ASSIGNEE if assigned_to isn't a user on this shop.

Request
curl -X POST "https://dev2025.flowstock.pro/api/v2/tasks" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com" \
  -H "X-Idempotency-Key: create-task-001" \
  -H "Content-Type: application/json" \
  -d '{"name": "Call the supplier", "type": "outbound_call", "priority": "urgent", "assigned_to": 7}'
Response
{
  "data": {
    "id": 501,
    "name": "Call the supplier",
    "status": "new"
  }
}
PATCH /tasks/{id}

Partial update

Scope: tasks.write

All fields optional, omitted = unchanged. Same rules/slugs as create, plus status becomes settable here.

Request
curl -X PATCH "https://dev2025.flowstock.pro/api/v2/tasks/501" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com" \
  -H "Content-Type: application/json" \
  -d '{"status": "completed"}'
Response
{
  "data": {
    "id": 501,
    "status": "completed"
  }
}

Users

Read-only. Discovers which FlowStock users a task can be assigned to — the API never creates or edits users.

GET /users

List users assignable within this shop

Scope: users.read

No query parameters. Returns every user assignable within the authenticated shop — the shop's owner, plus anyone attached via the shop_user pivot.

Request
curl "https://dev2025.flowstock.pro/api/v2/users" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com"
Response
{
  "data": [
    { "id": 1, "name": "Jane Doe", "email": "jane@example.com" },
    { "id": 7, "name": "John Smith", "email": "john@example.com" }
  ]
}

Meta

Answers "what are my pagination limits, sort options, filters and scopes" without hardcoding them client-side. Requires valid credentials but no specific scope — any active API user can call it.

GET /meta

Server capability info for this API version

No scope required.

Request
curl "https://dev2025.flowstock.pro/api/v2/meta" \
  -H "X-Api-Key: fs_xxx" \
  -H "X-Api-Email: you@example.com"
Response
{
  "data": {
    "pagination": { "default_per_page": 20, "max_per_page": 100 },
    "endpoints": {
      "purchase_orders": { "max_per_page": 100, "sortable_fields": [], "filters": ["status", "supplier_id", "date_from", "date_to", "search"] },
      "tasks": { "max_per_page": 100, "sortable_fields": [], "filters": ["status", "priority", "type", "assigned_to", "due_date_from", "due_date_to", "search"] }
    },
    "scopes": ["products.read", "products.write", "purchase_orders.read", "purchase_orders.write", "warehouses.read", "tasks.read", "tasks.write", "users.read"],
    "rate_limit": { "requests_per_minute": 300 }
  }
}