Global API & Protocol Specs
A deep dive into the strict HTTP contracts, the Filter AST query translation, and the HMAC-SHA256 webhook delivery systems that power the Aeion OS headless BaaS.
Protocol & Performance Metrics
Cursor Pagination
O(1) latency on multi-million row tables
Webhook Window
Strict 5-minute replay protection
Relation Depth
Capped at 5 levels via `?depth=` to bound population queries
SLA Target
< 50ms p95 latency for Weight-1 endpoints
1. The Filter AST Query Engine
Aeion eliminates the need for backend developers to write custom search endpoints.
- Bracket Notation & JSON Transport: The Global API supports complex queries natively. Clients can use bracket notation (?where[price][gt]=100&where[status][in]=paid,refunded) or URL-encoded JSON payloads.
- Schema-Validated SQL Translation: The query engine intercepts the filter string, parses it into an AST, validates the requested fields against the collection's compiled schema (rejecting unknown fields to prevent SQL injection/sniffing), and maps the safe AST directly to a highly-optimized, parameterized SQL query.
- 30+ Operators, One Grammar: The same AST vocabulary works on every collection — string (equals, contains, starts_with, in, ilike, search), numeric (gt, gte, between), array (overlaps, contains_any), JSON (json_contains, has_key), date (before, older_than), and logical (and, or, not) — so a client that learns the filter grammar once can query any table without a bespoke endpoint per collection.
- Cursor Pagination Mastery: Standard OFFSET pagination becomes exponentially slower as table sizes grow. Aeion enforces cursor-based pagination for high-volume endpoints, passing a Base64-encoded cursor (a stable sort key + row identifier) to keep database lookups at constant time regardless of page depth.
- `?explain=true` for Transparency: Any query can be introspected — append explain=true and the engine returns the generated SQL plus warnings (missing index, wide scan) instead of executing blindly. Developers debug a slow filter against the real query plan rather than guessing.
typescript
// Example: Validating Webhook Signatures on the Client Serverimport crypto from "crypto";
export function verifyAeionWebhook(req, secret) {const signature = req.headers["x-aeion-signature"]; // e.g., "v1=a1b2c3d4..."const timestamp = req.headers["x-aeion-timestamp"]; // unix seconds, e.g. "1710000000"
// Protect against replay attacksconst fiveMinutes = 5 * 60 * 1000;if (Date.now() - parseInt(timestamp, 10) * 1000 > fiveMinutes) {throw new Error("Webhook timestamp too old");}
const payload = `${timestamp}.${JSON.stringify(req.body)}`;const expectedSignature = `v1=${crypto.createHmac("sha256", secret).update(payload).digest("hex")}`;
// Use timingSafeEqual to prevent timing attacksif (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {throw new Error("Invalid signature");}return true;}2. The Two-Step Authentication Flow
Aeion handles enterprise identity through a strict isolation paradigm separating Global Identity from Tenant Scope.
- Step 1: System Auth: The client calls POST /api/auth/login using their global identity. The OS returns a stateful Refresh Token.
- Step 2: Tenant Selection: Because a user may belong to multiple tenants, they must call POST /api/auth/select-tenant, providing the Refresh Token and the target tenantId. The OS evaluates their membership and mints a highly-optimized, stateless JWT scoped to exactly the permissions (e.g., *:* for full access, or commerce:read for a single module) allowed for that specific tenant context.
- Why Two Steps, Not One: Folding tenant scope into the login credential would force a re-login every time a user switched organizations and would leak the full permission matrix into a long-lived token. Separating a stateful Refresh Token (global identity, revocable) from a short-lived stateless JWT (tenant-scoped, self-verifying) means the hot path — every subsequent API call — validates a signature with no database round-trip, while tenant switching is a single cheap exchange rather than a full re-authentication.
- Machine Identities: Server-to-server callers skip the interactive flow entirely and use scoped API keys created via /admin/auth/api-keys — each key carries its own permission grants, per-key rate limit, optional IP allowlist, and rotation path, so a leaked integration key is contained to exactly what it was provisioned to touch.
Security in a BaaS environment requires absolute silence regarding unauthorized data. If an authenticated user queries `GET /api/v1/products/123`, and the 3-Stage RBAC engine determines they do not have the required role to view it, the API will **not** return `403 FORBIDDEN`. Instead, the API returns a strict `404 NOT_FOUND`. This enforces the "Do Not Leak Existence" rule, mathematically preventing attackers from mapping out internal database structures by enumerating IDs.
To enforce Enterprise SLAs, the BaaS Protocol tiers rate limits by operation cost rather than treating every request identically. The Rate Limiter uses a Redis-backed sliding window: standard read endpoints default to 900 requests/minute per tenant, write endpoints to 300 requests/minute, with separate (typically lower) presets for auth, password-reset, and bulk/batch operations. Collections can override these platform defaults via an explicit `governance.rateLimit` config. This prevents a single poorly-written mobile app update from initiating a noisy-neighbor DoS attack against the shared infrastructure.
Frequently Asked Questions
No. All four protocols share one RBAC engine, one audit log, one rate-limiter, and one error envelope. A collection registered once is exposed through every protocol, and an authorization rule is written in exactly one place — so there's no risk of GraphQL enforcing a permission that REST forgot.
Returning `403 FORBIDDEN` confirms a record exists, which lets an attacker map internal IDs by enumeration. When the RBAC engine denies a read on `GET /api/v1/{slug}/:id`, the API returns a strict `404 NOT_FOUND` — the "Do Not Leak Existence" rule — so existence and access are indistinguishable to a caller without the right role.
Each outbound event is signed `HMAC-SHA256(timestamp + "." + body, sharedSecret)` and carries an `x-aeion-timestamp` header. The receiver recomputes the signature over the exact payload and compares with `timingSafeEqual`, then rejects any request whose timestamp is outside the 5-minute window — closing both forgery and replay in one check. The `verifyAeionWebhook` snippet above is the reference implementation.
No — limits are tiered by operation cost, not request count. A Redis-backed sliding window defaults read endpoints to 900 requests/minute per tenant and writes to 300/minute, with separate lower presets for auth, password-reset, and bulk/batch operations. A collection can override the platform defaults with an explicit `governance.rateLimit` config, and `429` responses carry `Retry-After` so a well-behaved SDK backs off precisely.
Yes. The surface is pinned at `/api/v1`; breaking changes only ship behind `/api/v2` with a migration window, field additions to existing endpoints are non-breaking by design, and the whole surface is described by an auto-generated OpenAPI 3.1 spec you can codegen a typed SDK from rather than hand-rolling a client per collection.
Build on the Contract, Not Around It
See the Filter AST, two-step auth, and signed webhooks against your own collections with our platform team.