Guides

File-based APIs

Export HTTP methods from api/*.ts. The filename is the path.

Each file under api/ becomes a route. api/planets.ts serves /api/planets. Nested folders work the same way: api/v1/items.ts serves /api/v1/items. Bracket segments become path params: api/invoices/[id].ts serves /api/invoices/:id and sets ctx.params.id. Exact files win over parameterized ones.

Export get, post, put, patch, or delete as defineApi(...) handlers.

api/planets.ts

import { planets } from "../database";
import { defineApi, z } from "@robodev-ai/sdk";
export const get = defineApi({
query: z.object({ climate: z.string().optional() }),
response: z.array(z.object({
id: z.string(),
name: z.string(),
})),
handler: async ({ db }) => db.select().from(planets),
});
export const post = defineApi({
body: z.object({ name: z.string().min(1) }),
handler: async ({ db, body }) => {
const [row] = await db.insert(planets).values(body).returning();
return row;
},
});

Handler context

  • db — Drizzle client for the project's tenant database.
  • email — injected project email client. Call email.send from the handler. See Email (/docs/email).
  • storage — injected project file storage. Call storage.upload / get / getUrl / delete / list. See Storage (/docs/storage).
  • env — project secrets as plaintext strings. See Secrets (/docs/secrets).
  • params — path parameters from api/invoices/[id].ts.
  • query — parsed query string when you pass query.
  • body — parsed JSON body when you pass body. Text fields for multipart.
  • headers — incoming request headers.
  • user — the signed-in project user, or null. Set auth on the definition.
  • rawBody — Buffer when defineApi({ rawBody: true }).
  • files — uploaded files when defineApi({ multipart: true }).
  • push — FCM client. See Push (/docs/push).
  • jobs — enqueue durable jobs. See Jobs (/docs/jobs).
  • schedule — { id } when the request has a valid schedule HMAC.

Responses

Return a plain JSON value for HTTP 200, or an envelope { status?, headers?, contentType?, body }. A string body is text/plain, Buffer/Uint8Array is bytes, and { base64 } decodes to bytes. Handlers default to a 10s timeout (max 30s) via timeoutMs.

api/motto.ts

import { defineApi } from "@robodev-ai/sdk";
export const get = defineApi({
handler: async () => ({
contentType: "text/plain",
body: "Ad astra",
}),
});

rawBody and multipart

  • rawBody: true — ctx.rawBody is the raw Buffer. JSON and urlencoded still parse into ctx.body. octet-stream stays unparsed. Not for multipart.
  • multipart: true — ctx.files[] with Buffer data (1 GiB request limit). Text fields land in ctx.body. rawBody + multipart is a deploy error.

auth on defineApi

Default is false (public). Use "required" or "optional" for Robodev Auth, or a custom verifier that reads headers and returns { id, email, name }. Required or a null custom verifier returns 401 and does not call the handler.

api/me.ts

import { defineApi, z } from "@robodev-ai/sdk";
export const get = defineApi({
auth: "required",
response: z.object({
user: z.object({
id: z.string(),
email: z.string(),
name: z.string().nullable().optional(),
}),
}),
handler: async ({ user }) => ({ user }),
});

Reserved project-host routes win over api/user.ts and api/user/** (Auth), and over api/storage/objects.ts and api/storage/objects/** (Storage serve). api/storage.ts registers as /api/storage. Only GET /storage/objects/{key} is reserved for serving files. defineApi handlers are served at /api/.... How-to: Auth (/docs/auth) and Storage (/docs/storage).

Swagger

After deploy, open /_robodev/docs on the project host for the generated OpenAPI. API-only projects also keep /docs and /swagger as Swagger. The APIs page links to that project-host Swagger. The dashboard also has Auth, Email, Secrets, Push, Automation, Storage, Logs, and Code. The Databases browser groups tables by Postgres schema (public, robodev_auth, and others).