Typed requests with typed()

Embed
Preview of the card
<a href="https://forumulate.com/reference/typed-requests"><img src="https://forumulate.com/reference/_api/public/orgs/046a9bc0-d530-43ae-9bae-af7945008cda/spaces/8042f004-e3d9-445f-b7a4-b6c1f86836e6/pages/47fb1938-6f5d-4676-96bb-3a435f02b330/social-card.svg?v=3e7ae33eec8b" alt="Typed requests with typed()" width="600" height="315"></a>
Every endpoint is documented in the API reference with a live try-it console.

The hand-written helpers (client.orgs, client.spaces, …) cover the common, stable surface. For any route in the API, use client.typed(method, path, init). It reads the machine-generated paths interface (the generated types, produced from the OpenAPI spec) and infers, for the exact (method, path) you pass:

  • which path params the route requires (filled into {placeholders}),

  • which query params it accepts,

  • the JSON body shape, and

  • the success response type.

The path and method both autocomplete, and the compiler rejects a body or param that doesn't match the spec.

Signature

typed<P extends keyof paths, M extends keyof paths[P] & HttpMethod>(
  method: M,
  path: P,
  init: TypedRequestInit<paths[P][M]>,
): Promise<TypedResponse<paths[P][M]>>

init carries up to four slots, each required exactly when the spec says so:

The resolved response type is the 200 body, else 201, else 202, else undefined for 204.

Example 1 — list a collection (GET with a path param)

const { spaces } = await forumulate.typed('get', '/orgs/{orgId}/spaces', {
  params: { orgId },
  query: { deleted: 'false', limit: '50' },
});

orgId is required and type-checked; spaces is typed from the spec.

Example 2 — create a resource (POST with a typed body)

const { space } = await forumulate.typed('post', '/orgs/{orgId}/spaces', {
  params: { orgId },
  body: {
    name: 'Docs',
    slug: 'docs',
    type: 'DOCS',        // enum union comes from the schema
    visibility: 'PUBLIC',
  },
});

A body field with the wrong name — or a type outside the allowed union — is a compile error.

Example 3 — write a piece of content, then publish it

// Create an item (a "piece" of a space — page, post, incident, …).
const { item } = await forumulate.typed('post', '/orgs/{orgId}/spaces/{spaceId}/items', {
  params: { orgId, spaceId },
  body: { title: 'Getting started', body: '# Hello\n\nWelcome.' },
});

// Publish it.
await forumulate.typed('post', '/orgs/{orgId}/spaces/{spaceId}/items/{itemId}/publish', {
  params: { orgId, spaceId, itemId: item.id },
});

Binary responses with raw

For endpoints that return a file rather than JSON, pass raw: true and read the Response yourself:

const res = await forumulate.typed('get', '/public/orgs/{orgId}/spaces/{spaceId}/pdf', {
  params: { orgId, spaceId },
  raw: true,
});
const pdf = await res.arrayBuffer();

Escape hatch: request()

Everything above delegates to a low-level, untyped method you can call directly for anything the typed layer can't express:

await forumulate.request('POST', `/orgs/${orgId}/spaces/${spaceId}/items/${itemId}/publish`, {
  body: {},
});

request<T>(method, path, { body?, query?, raw? }) — you build the path yourself and choose the response type. It applies the same auth header and error handling as the typed calls.

The Forumulate teamUpdated