TypeScript and Python SDKs

Typed clients generated from the OpenAPI contract: how to use them today from the repository, with booking, error, pagination and idempotency examples.

Updated:

Wagend has two SDKs that are generated from the contract packages/openapi/openapi.yaml, so their types always follow the API.

The SDKs are not published yet on npm or PyPI (Coming soon). Today you use them from the repository, as explained below. If you would rather not depend on them, the REST API works with any HTTP client.

TypeScript

The client (packages/sdk-ts) is a light wrapper over fetch with types for every route.

Install from the repository

cd packages/sdk-ts
npm ci
npm run build          # genera dist/

In your project, install it from that folder:

npm install /path/to/wagendapp/packages/sdk-ts

Book a slot

import { createWagendClient, WagendError } from "@wagend/sdk";

const api = createWagendClient({
  baseUrl: "https://api.wagend.app/v1",
  apiKey: process.env.WAGEND_API_KEY!, // wg_live_xxx
});

try {
  const { data: services } = await api.GET("/services");
  const serviceId = services!.data![0].id!;

  const { data: slots } = await api.GET("/slots", {
    params: { query: { service_id: serviceId, from: "2026-10-15T08:00:00-03:00", to: "2026-10-15T13:00:00-03:00" } },
  });
  const slot = slots!.data[0];

  // 10-minute hold
  const { data: hold } = await api.POST("/holds", {
    params: { header: { "Idempotency-Key": crypto.randomUUID() } },
    body: { service_id: serviceId, start: slot.start, party_size: 1, resource_ids: slot.resource_ids },
  });

  // Confirmation with the customer data
  const { data: booking } = await api.POST("/holds/{id}/confirm", {
    params: { path: { id: hold!.id! }, header: { "Idempotency-Key": crypto.randomUUID() } },
    body: { customer: { name: "Carlos", phone: "+5491155550000", locale: "es" } },
  });
  console.log(booking!.status); // "confirmed"
} catch (err) {
  if (err instanceof WagendError) {
    console.error(err.status, err.code, err.retryAfter);
  } else {
    throw err;
  }
}
  • Authentication: the client adds Authorization: Bearer for you.
  • Idempotency: on every write it adds an Idempotency-Key if you do not pass one. To retry an operation, pass the same key yourself in both attempts (valid for 24 hours). The TypeScript type declares it in params.header for the routes that require it.
  • Errors: any non-2xx response throws WagendError with .status, .code, .problem (RFC 9457) and .retryAfter (seconds, on 429).

Pagination

let cursor: string | undefined;
do {
  const { data: page } = await api.GET("/bookings", { params: { query: { limit: 50, cursor } } });
  for (const booking of page?.data ?? []) console.log(booking.id);
  cursor = page?.next_cursor ?? undefined;
} while (cursor);

Customers and actions

// Search and create customers (scopes customers:read and customers:write)
const { data: found } = await api.GET("/customers", { params: { query: { q: "carlos" } } });
await api.POST("/customers", { body: { name: "Ana", phone: "+5491155551111", tags: ["vip"] } });

// Run a stage action, for example assigning a delivery to a courier (scope bookings:write).
// "atribuir" is the key of that action in the deliveries template.
await api.POST("/bookings/{id}/actions/{key}", {
  params: { path: { id: taskId, key: "atribuir" }, header: { "Idempotency-Key": crypto.randomUUID() } },
  body: { data: {}, resource_id: courierResourceId },
});

Action keys (key) are defined by your business's stage configuration: look them up with GET /stage-config.

Python

The client (packages/sdk-py) uses httpx and generated Pydantic v2 models.

Install from the repository

pip install /path/to/wagendapp/packages/sdk-py
# or, with uv:
uv pip install /path/to/wagendapp/packages/sdk-py

Book a slot

from datetime import UTC, datetime
from wagend import Wagend, WagendError

with Wagend("https://api.wagend.app/v1", "wg_live_xxx") as api:
    try:
        service = api.list_services()[0]
        slots = api.list_slots(
            str(service.id), datetime(2026, 10, 15, 8, tzinfo=UTC), datetime(2026, 10, 15, 13, tzinfo=UTC)
        )

        hold = api.request(
            "POST", "/holds",
            json={"service_id": str(service.id), "start": slots[0].start.isoformat(), "party_size": 1},
            idempotency_key="booking-carlos-2026-10-15",
        )
        booking = api.request(
            "POST", f"/holds/{hold['id']}/confirm",
            json={"customer": {"name": "Carlos", "phone": "+5491155550000"}},
        )
        print(booking["status"])
    except WagendError as err:
        print(err.status, err.code, err.retry_after)
  • list_services() and list_slots() return typed models. For the other routes use api.request(method, path, params=…, json=…); you can validate the response with wagend.models.
  • Idempotency: writes carry an Idempotency-Key (one is generated if you do not pass idempotency_key). Retry with the same key.
  • Errors: WagendError with .status, .code, .problem and .retry_after (on 429).
  • Pagination: api.paginate("/bookings", params={"limit": 50}) walks every page.
for booking in api.paginate("/bookings", params={"limit": 50}):
    print(booking["id"])

Rate limits

The limit is per key and per project. On a 429, wait retry_after / retryAfter seconds before retrying. See Conventions.

Regenerating the SDKs

If the contract changes, make sdk regenerates both from openapi.yaml and make sdk-check fails if they are out of date.