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/api

Authentication

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

ParamTypeDescription
filterJSONTyped filter expression
sortstringe.g. -createdAt,name
fieldscsvSparse projection
includecsvExpand relations
pageintPage number (1-indexed)
pageSizeintItems per page (max 200)
cursorstringCursor pagination (preferred for large sets)
includeTotalboolAdd 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.yaml

The 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.