All Stories
2026-09-25•9 min read

Serving · FastAPI · OpenAPI · TypeScript · Next.js · Pixeltable Cloud · API Keys · TableModel

How to Serve a Typed AI Endpoint with Pixeltable

Turn a Pixeltable table into a typed HTTP endpoint: serve it locally, deploy it to Pixeltable Cloud, and call it from Next.js with generated TypeScript types.

Pierre Brunelle

Pierre Brunelle

Pixeltable Team

Summary: A Pixeltable service turns tables into HTTP routes. You declare the table and its routes in one app.py, run two commands to serve it locally, and three to host it on Pixeltable Cloud. Every service publishes an OpenAPI schema, so a TypeScript app gets generated types for every route, and a hosted service accepts only requests that carry a Pixeltable API key. This post walks the whole path, from the table to a typed call in a Next.js app.

The reference docs are HTTP serving and Deploy to Pixeltable Cloud. Everything below uses Pixeltable 0.7.11 or later.

What a service gives you#

A route is a table operation exposed over HTTP. You choose the operation and the columns; Pixeltable writes the handler, the request and response models, and the schema.

RouteWhat a request doesTypical use
add_insert_routeInserts a row, runs its computed columns, returns the outputs you listIngest a document, an image, a transcript
add_compute_routeRuns the computed columns without storing a rowClassify, summarize, or embed on demand
add_update_routeUpdates a row by key and recomputes what depends on itCorrect a field, rerun a model
add_delete_route, query routesDelete rows, or return the rows a query selectsCleanup, search, lookups

Routes can accept file uploads, return a file instead of JSON (return_fileresponse=True), or run in the background and return a job handle (background=True).

Declare the table and its routes#

This is the Quickstart application. pxt service example --out app.py writes it for you.

python

Each column named in outputs becomes a typed field of the response, computed columns included. Swap pxt.String for pxt.Image, pxt.Video, pxt.Audio, or pxt.Document and the same routes serve a media pipeline.

Serve it locally#

bash

pxt schema update creates the tables; it does not start HTTP. pxt service update starts HTTP; it does not create tables. Pass -f whenever a script, CI job, or coding agent runs these commands: without a terminal to confirm in, commands that change things refuse to proceed.

The service gets its own port, so read the endpoint from pxt service list --json rather than assuming port 8000. Interactive docs are at /docs and the schema at /openapi.json. A local service needs no key.

Deploy the same file to Pixeltable Cloud#

Pixeltable Cloud is in Limited Beta; email [email protected] for an account. Sign in with pxt login, or create an API key in the dashboard and export it as PIXELTABLE_API_KEY. A key in the environment or in the config file takes precedence over a pxt login session, so remove a stale one before you sign in.

Name the hosted database in pixeltable.toml:

toml

Then run the three commands:

bash
  1. pxt db update creates the database and uploads the project. It rebuilds the image only when your dependencies changed, which is the slow step; a code change is just an upload.
  2. pxt schema update creates the tables in the hosted database.
  3. pxt service update starts the hosted service.

The hosted image installs the Pixeltable version your lockfile (uv.lock or requirements.txt) pins, so pin 0.7.11 or later there too. pxt service list pxt://acme:prod --json prints the service URL, and pxt service diff app.py pxt://acme:prod shows what would change before you apply it. The docs list what to run after each kind of change.

Give your app its own key#

Your app server needs a key that does not expire and does no more than the app needs. Create one per app and environment:

bash

With the grant, the key belongs to the organization and can call the ingest service and nothing else. It cannot list services, reconfigure them, or reach another database. Keys with grants are a preview; until your organization has them, omit --grant for a key that acts as you. The secret prints once. Store it in your app server's environment as PIXELTABLE_API_KEY, never under a NEXT_PUBLIC_ name that would ship it to the browser. See pxt key for the full grant table.

Generate TypeScript types from the schema#

A hosted schema needs the key to download. It declares the X-api-key header on every route, so generated clients know how to authenticate:

bash

File responses are typed as binary, and the background job status route is in the schema, so polling is typed too. Regenerate the file whenever the Python routes change, and let your TypeScript check catch any mismatch before you deploy.

Call it from a Next.js server#

Generated types do not check a raw fetch, so call routes through openapi-fetch. Run npm install openapi-fetch server-only and add src/lib/pixeltable-client.ts:

typescript

The key is set once on the client, so no call has to pass it. import 'server-only' makes the build fail if a client component imports the module. Call it from a Server Action:

typescript

The same module calls a local service: point PIXELTABLE_SERVICE_URL at the local endpoint and leave PIXELTABLE_API_KEY unset. A deployment on Vercel or any other host needs the hosted endpoint, since it cannot reach your laptop. In the Cloud dashboard, the service page's API docs lets you try every route as your signed-in user, and Use this endpoint gives server-side snippets.

Handle errors and retries#

PixeltableError.body is the parsed error response. Branch on the shape of its detail, not on the status:

detail isWhen
An object with error_code, message, retryable, sometimes retry_afterA Pixeltable runtime error, or an error the Cloud gateway answers itself
A stringA missing row
An arrayA request that failed validation

The gateway answers these itself, with the same object:

StatusMeaningRetry?
401Missing or invalid keyNo: fix the key
403The key has no access to this serviceNo: add a grant
404No service at this URLNo: check the endpoint
429The key is rate limitedYes, after Retry-After
503The service is not runningYes

Retry a write only when detail.retryable is true, waiting retry_after seconds when it is present. That one rule covers service and gateway errors alike.

Files, uploads, and background jobs#

  • File responses. A return_fileresponse=True route needs parseAs: 'blob', or parseAs: 'stream' to pipe the body through your Route Handler with the upstream Content-Type. Without it the client tries to parse the file as JSON.
  • Uploads. Generated types describe upload fields as strings, so send uploads with fetch, a FormData body, and the same X-api-key header. Let fetch set the multipart boundary.
  • Background jobs. A background=True route returns an id and a job_url right away. Polling is a typed call on the same client: pixeltable.GET('/_pxt/jobs/{job_id}', { params: { path: { job_id: id } } }) returns pending, done with the result, or error with an error_detail that follows the same retry rule.
  • Media outputs. Image and video URLs in a hosted JSON response are signed and expire after an hour. Render them directly; do not store them.

Secrets, logs, and changes#

Provider keys such as OPENAI_API_KEY are secrets, not API keys. Set them in the dashboard or with the CLI, then restart so the service reads them:

bash

A database secret overrides an organization secret of the same name. After a code change, run pxt db update and then pxt service update; after a column change, add pxt schema update between them.

The checklist#

  1. Pin Pixeltable 0.7.11 or later in the project's lockfile.
  2. Serve locally first, with -f in anything scripted.
  3. Sign in with pxt login, or export one API key; a stored key wins over the login.
  4. Deploy with pxt db update, pxt schema update, pxt service update.
  5. Create one key per app and environment, granted only the service it calls.
  6. Generate types from the hosted schema and regenerate after every route change.
  7. Call the service from server code only, through the typed client.
  8. Check from outside: a request without the key returns 401 with a JSON detail, and one with the key returns 200.
  9. Retry only when detail.retryable is true.
  10. Set provider secrets, then restart the database.

Full reference: HTTP serving, Deploy to Pixeltable Cloud, and the CLI. For why the same file runs locally and in the cloud, read why the local-cloud loop matters.

Declarative. Multimodal. Incremental.

Focus on innovation, not infrastructure.

10-minute tutorial · Join community