PRODUCTS

KEYWORDS

Introducing DoltHub API v2

We are excited to announce the release of the DoltHub API v2, a modern, versioned REST API defined by an OpenAPI 3.1 spec. It is live now under /api/v2/... alongside our existing v1alpha1 API, which will continue to be supported for the foreseeable future.

Motivation#

DoltHub has had a public REST API for years, but the story of v1alpha1 explains a lot about the shape it ended up in. It started as a single endpoint for running SQL read queries against a database, built on top of the GraphQL models and resolvers that already powered the DoltHub UI. As DoltHub grew and customers asked for more, we kept adding endpoints in the same pattern: piggyback on whatever the UI was already using, expose it under /api/v1alpha1/..., and move on. There was no contract, no stability guarantee, and no separation between what the API returned and what the website’s GraphQL layer returned. Nothing stopped a change made to a GraphQL resolver for a UI feature from silently reshaping an endpoint.

That approach worked well enough when the API was a convenience layer for a few power users, but it isn’t the right shape for where DoltHub is going next. We think Dolt is the database for agents, and for agents to run real version-control workflows against DoltHub, like cloning, branching, importing, opening pull requests, and merging, they need a REST API they can rely on. Building that is the goal of v2.

Concretely, three things about v1alpha1 needed to change.

1. Consistent semantics#

v1alpha1’s endpoints weren’t consistent with each other. Most error paths returned a 400 regardless of the actual failure, response shapes varied endpoint to endpoint, and each async producer had its own polling contract (?operationName=<name>, positional path params, different response bodies).

In v2, all of this is unified: real HTTP status codes for what actually happened, one problem details shape for every non-2xx response, one success envelope, one cursor-pagination convention for list endpoints that support it, and a single Operation resource that clients poll the same way regardless of whether they’re waiting on a fork, a merge, an import, or a SQL write.

2. A stable contract, decoupled from the UI#

v1alpha1’s endpoints shared models and resolvers with the DoltHub website’s GraphQL layer, which is why a UI change could reshape an API response by accident.

v2 is defined by an OpenAPI 3.1 spec that we version alongside the server code, and that spec is the source of truth for the request and response shapes, the error codes, and the documentation. v2’s handlers skip the GraphQL layer entirely and call the underlying dolthubapi gRPC service directly, so there is no path for a UI-driven GraphQL change to reach the API surface. The spec is versioned, so callers know exactly what they are building against.

3. Verifiable end-to-end#

v1alpha1 had no formal contract to verify anything against, so drift between what the documentation said, what the code did, and what the wire returned could ship undetected.

Because v2’s spec is the source of truth, everything downstream can be verified against it in CI. Request bodies are validated at runtime against the spec’s schemas. Response shapes are asserted in contract tests. Generated TypeScript types have to match the checked-in output or the build fails. A breaking-change check flags any diff against the previous version of the spec on every pull request. If v2 says an endpoint behaves a certain way, callers can rely on it because the same file drives documentation, types, validation, and tests.

What’s new#

Everything downstream is generated from an OpenAPI 3.1 spec we version alongside the server code: the published docs, the TypeScript DTO types, the runtime request validation, and the contract tests. Adding an endpoint means editing the spec first. Anything that doesn’t match won’t build. This has made v2 easier to develop against and keep honest.

A few concrete things clients will notice:

Uniform success envelope#

Every 2xx response has the shape { "data": ..., "meta": ... }. data is the resource (or list of resources) and meta is optional metadata (currently just next_page_token for paginated lists). There are no unenveloped success bodies.

Cursor pagination#

Every list endpoint returns the same envelope shape. Where pagination is supported, the response includes meta.next_page_token, which the caller passes back as the page_token query param to fetch the next page. Not every list is paginated today (for example, list forks returns the full set in one response), but adding pagination to one is an additive change.

Async operations are one resource#

Every async endpoint (SQL write, import, merge, fork) returns 202 Accepted with an OperationRef:

{
  "data": {
    "id": "repositoryOwners/dolthub/repositories/us-jails/jobs/abc123",
    "href": "https://www.dolthub.com/api/v2/operations/repositoryOwners/dolthub/repositories/us-jails/jobs/abc123"
  }
}

Clients follow href to GET /api/v2/operations/{id} and poll until status is succeeded or failed. Every operation type has the same polling contract.

Uniform bearer auth#

Personal access tokens and OAuth access tokens are both sent as Authorization: Bearer <token>. Endpoints return 401 for missing or invalid credentials or 403 for insufficient permission. No more per-endpoint auth quirks.

Snake_case everywhere#

Field names are snake_case across the board. No mixed-case surprises.

Example: fork a database#

Here’s an end-to-end async flow using v2. We will fork a database, poll the operation, and see what a success and a failure look like. This example assumes you have already created an API token in your DoltHub settings.

First, create the fork:

curl -i -X POST 'https://www.dolthub.com/api/v2/databases/dolthub/us-jails/forks' \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $DOLTHUB_TOKEN" \
  -d '{"owner": "taylor"}'

The response is a 202 with an OperationRef:

{
  "data": {
    "id": "repositoryOwners/dolthub/repositories/us-jails/jobs/e20b1e69-bfb6-4f3e-885c-9b9c278bc222",
    "href": "https://www.dolthub.com/api/v2/operations/repositoryOwners/dolthub/repositories/us-jails/jobs/e20b1e69-bfb6-4f3e-885c-9b9c278bc222"
  }
}

Follow href to poll the operation until it terminates:

curl -s 'https://www.dolthub.com/api/v2/operations/repositoryOwners/dolthub/repositories/us-jails/jobs/e20b1e69-bfb6-4f3e-885c-9b9c278bc222' \
  -H "Authorization: Bearer $DOLTHUB_TOKEN"

While the operation is queued:

{
  "data": {
    "id": "repositoryOwners/dolthub/repositories/us-jails/jobs/e20b1e69-bfb6-4f3e-885c-9b9c278bc222",
    "type": "fork",
    "status": "queued",
    "created_at": "2026-07-09T12:00:00Z",
    "cancelable": false
  }
}

On success:

{
  "data": {
    "id": "repositoryOwners/dolthub/repositories/us-jails/jobs/e20b1e69-bfb6-4f3e-885c-9b9c278bc222",
    "type": "fork",
    "status": "succeeded",
    "created_at": "2026-07-09T12:00:00Z",
    "cancelable": false
  }
}

Once the fork has succeeded, the new database is available through the standard database endpoint:

curl -s 'https://www.dolthub.com/api/v2/databases/taylor/us-jails' \
  -H "Authorization: Bearer $DOLTHUB_TOKEN"
{
  "data": {
    "owner": "taylor",
    "name": "us-jails",
    "visibility": "public",
    "fork_network_count": 5,
    "star_count": 0,
    "size_bytes": 12582912,
    "parent": {
      "owner": "dolthub",
      "name": "us-jails"
    },
    "network_root": {
      "owner": "dolthub",
      "name": "us-jails"
    }
  }
}

And on fork failure the same shape carries an error:

{
  "data": {
    "id": "repositoryOwners/dolthub/repositories/us-jails/jobs/e20b1e69-bfb6-4f3e-885c-9b9c278bc222",
    "type": "fork",
    "status": "failed",
    "created_at": "2026-07-09T12:00:00Z",
    "cancelable": false,
    "error": {
      "status": 422,
      "code": "OPERATION_FAILED",
      "title": "Operation failed",
      "detail": "Owner already owns a repository in the same network"
    }
  }
}

The same three-step flow works for imports, SQL writes, and pull request merges.

Documentation#

You can find the full reference at dolthub.com/docs/products/dolthub/api/v2. Every endpoint, request and response schema, error code, and security scheme is rendered directly from the OpenAPI spec, so there is no drift between what the docs say and what the server actually does. If you want to generate a typed client, the OpenAPI spec itself lives in our GitHub repo. You can grab it from there and feed it to your generator of choice.

What’s next#

v2 covers the DoltHub REST surface end to end today. Reads, synchronous writes, and async operations are all live under /api/v2/..., ready to build against. If you are already using v1alpha1, our migration guide walks through the shape changes endpoint by endpoint.

We plan to use the v2 template to build Hosted Dolt’s first public REST API (coming soon!), and it will follow the same contract-first pattern with the same error model, response envelope, and async operation shape. Building against DoltHub and Hosted Dolt should feel like the same API surface.

v1alpha1 is going to stick around, so this is not a forced migration. But if you are building against DoltHub today, v2 is the surface that will keep growing. Give it a try and let us know what you think on Discord or by filing an issue on GitHub.