HTTP endpoints are here! Convex 0.9.0 extends the platform by allowing you to build your HTTP API right within Convex.

The full list of changes in 0.9.0 is:

  • HTTP Endpoints
  • File Storage in the Dashboard
  • Breaking: API Changes in Actions
  • Breaking: runAt Now Uses Milliseconds

Details below.

HTTP Endpoints

You can now define HTTP endpoints right within Convex!

This enables you to build a HTTP API that interacts with all of your Convex data. This is useful for building a public API or receiving webhooks from external applications.

HTTP endpoints are defined with httpEndpoint. Endpoints receive the Request object; can call Convex functions with runQuery, runMutation, and runAction; and return a Response object.

To expose the endpoints, use httpRouter in convex/http.js or convex/http.ts. The endpoints will be available at https://<your deployment name> The domain signifies that these are your routes running under Convex, as opposed to which serves the Convex API.

Here’s an example of a router with a single endpoint:

import { httpRouter } from "convex/server";
import { httpEndpoint } from "./_generated/server";

const postMessage = httpEndpoint(async ({ runMutation }, request) => {
  const { author, body } = await request.json();

  await runMutation("sendMessage", `Sent via HTTP endpoint: ${body}`, author);
  return new Response(null, {
    status: 200,

const http = httpRouter();

  path: "/postMessage",
  method: "POST",
  handler: postMessage,

export default http;

You can call this endpoint with:

export DEPLOYMENT_NAME=... # example: "tall-sheep-123"
curl -d '{ "author": "User 123", "body": "Hello world" }' \\
    -H 'content-type: application/json' "https://$"

To learn more, read the docs!

File Storage in the Dashboard

We’ve added a new page to the dashboard for managing file storage. Now you can upload, view, and delete files from the dashboard. Check it out!

Breaking: API Changes in Actions

We’ve renamed two functions used in Convex actions:

  1. ActionCtx.mutationActionCtx.runMutation
  2. ActionCtx.queryActionCtx.runQuery

Now, actions should be defined like:

export default action(async ({ runMutation, runQuery }) => {
  // Action logic. Call `runMutation` and `runQuery` in here!

This change makes actions consistent with how HTTP endpoints call queries and mutations. It also differentiates calling a query or mutation (runQuery or runMutation) from defining a query or mutation (query or mutation).

Breaking: runAt Now Uses Milliseconds

We’ve changed the API for scheduling functions to run at a timestamp. Now scheduler.runAt accepts a timestamp in milliseconds since the epoch instead of seconds since the epoch. This is more consistent with runAfter which accepts delay in milliseconds and timestamps in Convex in general.