REST API
OpenAPI 3.1, idiomatic resources, a consistent error contract — compiled from your entity model, never hand-written.
Every Vadyl entity gets a REST surface compiled from its canonical contract. Routes follow a consistent shape; auth, pagination, error contract, and rate limiting are uniform across the entire surface.
Base URL
https://api.vadyl.app/v1
# or self-hosted:
https://your-domain.com/apiAuthentication
Send a bearer token in the Authorization header:
import { createClient } from "@vadyl/sdk";
const vadyl = createClient({
apiUrl: "https://api.vadyl.app/v1",
token: process.env.VADYL_TOKEN!,
tenant: "acme",
project: "billing",
});
const me = await vadyl.identity.me();Tokens come from the identity provider you bound (Auth0, Clerk, WorkOS, custom OIDC). API keys are also supported for machine-to-machine auth — see Identity.
Resource conventions
GET /api/{entity} # list
POST /api/{entity} # create
GET /api/{entity}/{id} # read
PUT /api/{entity}/{id} # update (full)
PATCH /api/{entity}/{id} # update (partial)
DELETE /api/{entity}/{id} # delete
# Nested via relations
GET /api/{entity}/{id}/{relation}
POST /api/{entity}/{id}/{relation}
# Custom operations
POST /api/{entity}/{id}/{operation}
POST /api/operations/{operation}List query parameters
| Param | Type | Description |
|---|---|---|
filter | JSON | Typed filter expression |
sort | string | e.g. -createdAt,name |
fields | csv | Sparse projection |
include | csv | Expand relations |
page | int | Page number (1-indexed) |
pageSize | int | Items per page (max 200) |
cursor | string | Cursor pagination (preferred for large sets) |
includeTotal | bool | Add totalCount to response |
Listing entities
The same canonical operation across every client:
const recent = await vadyl.orders.list({
filter: { status: { in: ["paid", "fulfilled"] }, total: { gt: 100 } },
sort: [{ field: "createdAt", direction: "desc" }],
pageSize: 50,
include: ["customer"],
});Pagination
Responses use a typed envelope:
{
"data": [ ... ],
"page": { "number": 1, "size": 50, "totalCount": 1234, "next": "cursor:..." }
}Cursor pagination is preferred for large datasets — stable across concurrent writes and faster than offset pagination on big tables.
Creating entities
const customer = await vadyl.customers.create(
{ email: "ada@example.com", name: "Ada Lovelace" },
{ idempotencyKey: `signup:${session.id}` },
);Error contract
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Order.total must be > 0",
"reasonCode": "OrderValidation.NonPositiveTotal",
"field": "total",
"correlationId": "01HXY...",
"explainUrl": "/api/Explainability/access?correlationId=01HXY..."
}
}Every error carries a stable typed code, a stable reasonCode, and a correlationId you can cross-reference in audit, operational, and explainability surfaces.
Rate limiting
Per-project quotas are enforced through canonical billing. When a quota is exceeded:
HTTP/1.1 429 Too Many Requests
X-Vadyl-Quota-Reset: 2026-05-08T00:00:00Z
X-Vadyl-Quota-Kind: read
X-Vadyl-Quota-Limit: 1000000
X-Vadyl-Quota-Used: 1000001
{ "error": { "code": "QUOTA_EXCEEDED", "reasonCode": "Read.MonthlyLimit" } }OpenAPI spec
Every project publishes its full OpenAPI 3.1 spec at:
GET /api/openapi.json
GET /api/openapi.yamlThe spec is compiled from your entity model and updates atomically on every publication. Generate clients with openapi-generator, point Postman at it, or import into an API gateway — it's always current.