.dev
REST API

API conventions

Hosts, pagination, the filterator query language, batch operations, errors and realtime.

The Feeef API is a REST JSON API. The interactive, always-current endpoint reference lives at api.feeef.org/openapi (OpenAPI + Scalar UI, also served as /openapi.json and /openapi.yaml). This page documents the conventions that apply across all endpoints.

Base URLs & versioning

EnvironmentBase
Production (dedicated API host)https://api.feeef.org/v1
Production (path-prefixed mount)https://…/api/v1
Local developmenthttp://localhost:3333/api/v1

Requests should send Accept: application/json. Resources follow REST-ish naming: GET /products, POST /orders, PUT /stores/:id, DELETE /apps/:id, plus RPC-style sub-actions (POST /orders/calculate, POST /store_templates/:id/install).

Pagination

List endpoints accept page and limit and return the Adonis paginator envelope:

{
  "data": [ { "id": "..." } ],
  "meta": { "total": 128, "currentPage": 1, "perPage": 50 }
}

Some lightweight lists return a bare array instead — both SDKs normalize the two shapes into a single ListResponse with data, total, page, limit.

Filtering: flat params & the filterator

Most lists accept flat query params (e.g. products: store_id, q, status, category_id, price_min, price_max, in_stock, order_by=field:dir, page, limit).

For arbitrary conditions, pass a filterator — a URL-encoded JSON query:

{
  "filtering": {
    "condition": "and",
    "filters": [
      { "field": "status", "operation": "equals", "value": "published" },
      { "field": "price", "operation": "lessOrEqual", "value": 5000 }
    ],
    "groups": []
  },
  "ordering": [{ "field": "sold", "dir": "desc" }],
  "paging": { "limit": 12, "offset": 0 }
}
curl "https://api.feeef.org/v1/products?store_id=STORE&filterator=%7B...%7D"

Operators (long names preferred; short aliases in parentheses):

OperatorMeaning
equals (eq) / notEquals (neq)Equality
contains, startsWith, endsWithString matching
greaterThan (gt), greaterOrEqual (gte), lessThan (lt), lessOrEqual (lte)Comparison
inList (in), notInSet membership — pass values: [...]
isNull, isNotNullNull checks (no value)

Field names are camelCase and mapped to snake_case columns server-side. Groups nest with their own condition for (A and B) or C trees. The same filterator surface powers the app list (GET /apps), orders, products and more.

Batch operations

Bulk endpoints use AIP-style :actions on the collection:

EndpointPurpose
POST /{resource}:batchDeleteDelete many by ids
POST /{resource}:batchUpdateUpdate many with an updateMask + shared fields
POST /{resource}:batchCreateCreate many
POST /inventory/reservations:batchReleaseDomain-specific batch actions

Responses are a partial-success envelope:

{
  "resources": [ { "id": "..." } ],
  "failedRequests": {
    "orders/abc123": { "code": 9, "message": "FAILED_PRECONDITION: ..." }
  },
  "summary": { "total": 10, "succeeded": 9, "failed": 1 }
}

HTTP 200 does not mean every item succeeded — always inspect summary and failedRequests. A fully-failed batch may come back as 400 with the same envelope; both SDKs parse it either way.

Errors

StatusShapeMeaning
401{ "errors": [{ "message": "Unauthorized access" }] }Missing/expired token
403{ "error": "insufficient_scope", "requiredScope": "store.integrations" } + WWW-AuthenticateToken lacks a required scope (RFC 6750)
404{ "message": "Row not found" }Unknown resource (or hidden for privacy)
422{ "errors": [{ "message": "...", "rule": "required", "field": "name" }] }Validation (VineJS) — one entry per violation
429rate-limit errorSensitive endpoints (signup, auth-code consume) are rate limited

OAuth endpoints return RFC 6749-style bodies instead: { "error": "invalid_grant", "error_description": "..." }.

Realtime (SSE)

The backend broadcasts CRUD events over Server-Sent Events (AdonisJS Transmit) at the API origin without the version prefix: https://api.feeef.org/__transmit/events.

  • Channels follow resource paths, e.g. stores/{storeId}/orders, users/{userId}.
  • Events are { "event": "created" | "updated" | "deleted", "data": { ... } }.
  • Subscribing requires the bearer token on the subscription POST.

Both SDKs ship helpers — see JS realtime and the Dart Feeef.instance.realtime.

File uploads

POST /services/storage/upload accepts multipart uploads (field file, optional folder) and returns { "url": "https://storage.feeef.net/..." }. The SDKs wrap it with progress callbacks.

OpenAPI

The complete generated reference (rebuilt from the backend spec with npm run api:regen):

Guides by area

On this page