Reference

REST API

Every claim on this page is checked against a live generated OpenAPI spec — call generate_openapi_spec (MCP) or open API Reference in the dashboard for your project's exact, current contract.

Base URL

https://api.aaly.io/<your-slug>/<project>

Both segments come from your account and your project — don't guess them. Get the exact URL from generate_openapi_spec (MCP) or API Reference in the dashboard; it's the servers[0].url in the spec.

Every entity gets five endpoints once its fields exist:

GET    /{entity}          List, with filtering, sorting, and pagination
POST   /{entity}          Create
GET    /{entity}/{id}     Get one — always immediately consistent
PATCH  /{entity}/{id}     Partial update
DELETE /{entity}/{id}     Soft delete — cascades to nested children

Authentication

Every request needs Authorization: Bearer <token>, where the token is either a JWT from /auth/signin or /auth/signup, or an API key (aaly_<id>_<secret>) from POST /auth/keys. Both are accepted on every endpoint identically. See Auth.

Filtering

GET /{entity}?{field}[operator]=<value> — a bare ?{field}=<value> means [eq]. Multiple query parameters are AND-ed. A reference field is always filterable, including the system audit references _createdBy / _updatedBy / _deletedBy; any other field type (including enum) is filterable once created with search: true.

Field typeSupported operators
reference, enum, boolean, listeq, ne, in, nin
number, date, datetimeeq, gt, gte, lt, lte
text (with search: true)eq, starts — both prefix-match

in/nin take a comma-separated list with OR semantics within the field. ne/nin need another positive filter elsewhere in the request to drive the query. Not supported: like/ends on text, in/nin/ne on number/date/datetime, nested/dot-path filtering (customer.name=) — each returns a clean 400 rather than a silently wrong answer.

Filter results read from an asynchronously-maintained search index — a just-written record may not be immediately filterable (typically sub-second lag). GET /{entity}/{id} is always immediately consistent regardless.

Sorting

?sort={field} or ?sort=-{field} — descending with the leading -. Only on a number/date/datetime field, and only when that same field also has a matching filter condition in the same request. Arbitrary unfiltered sort returns a 400.

Facets

?facets=true (every faceted field) or ?facets=<field1>,<field2> (a selected subset) on an unfiltered collection GET. A field with search: true on an eligible type (enum/boolean/list/reference/ date/datetime) is faceted automatically:

{ "status": { "open": 42, "done": 3 } }

Facet counts are a global per-entity distribution, which is why the parameter is unfiltered-only — combining facets= with any real filter returns a 400 (FACETS_REQUIRES_NO_FILTERS) rather than silently describing the wrong result set.

Pagination

limit (1–200, default 50) and offset. Every list response:

{ "items": [ /* ... */ ], "count": 3, "total": 41, "limit": 50, "offset": 0 }

count is items in this page; total is every record matching the filter.

Errors

StatusShapeWhen
400{ "error", "field", "message" }Validation failure, or an unsupported operator/filter combination
401—Missing or invalid bearer token
403—Cross-tenant access, or a tenant rule violation
404—Record not found
409{ "error", "field", "message" }A unique field's value already exists
500{ "error" }Internal error

What's next

Auth for the identity endpoints · Concepts for field types and references · Limits for what these endpoints don't do yet (bulk operations, cursor pagination, full-text ranking).