Skip to Content
We are live but in Staging 🎉
Conventions

API Conventions

Use this page as the baseline for all K3 API calls.

Transport and Endpoints

  • HTTP: https://api.data.dodil.io
  • gRPC: rpc.data.dodil.io:443
  • Examples in these docs use the HTTP URL. The gRPC door is a separate listener serving the same methods over the same Authorization header — you address them by RPC name rather than by path (see Using gRPC).

The HTTP API is the simplest way in — plain JSON over HTTPS — and is the default tab in these docs; every method is also available over gRPC (see Using gRPC). Export your token once and the examples work:

export DODIL_TOKEN="<your-token>"

Auth

Send one header on authenticated HTTP requests:

  • Authorization: Bearer <token>

That’s it. Your organization context is encoded in the credential — K3 derives it server-side, so you never send x-organization-id yourself. (For S3 byte-plane requests, you can alternatively authenticate with SigV4 — see Storage S3 Compatibility.)

Interactively, get a token with dodil auth login. Programmatically — CI, apps, agents — mint a JWT with the OAuth client-credentials flow; see Get an Access Token .

dk_ API keys are a data-plane credential. The control plane verifies Authorization: Bearer as a Keycloak JWT only — a dk_… value sent as the Bearer here fails with 401 authentication failed. API keys are accepted on the wire adapters and on the object gateway — see Connect & wire adapters and Auth and Access → Auth modes.

Issue an API key for the data plane with:

dodil auth apikey issue --name ci --role k3.editor # secret shown once

See CLI Basics → API keys.

Using gRPC

Every method is also reachable over gRPC as dodil.data.<pillar>.v1.<Service>/<Method> (e.g. dodil.data.storage.v1.StorageService/ListBuckets). Install grpcurl (brew install grpcurl) and set the endpoint once:

export K3_GRPC="rpc.data.dodil.io:443"

Both endpoints are TLS by default — no -plaintext flag. grpcurl discovers the schema via server reflection, so no .proto is needed:

grpcurl -H "Authorization: Bearer $DODIL_TOKEN" $K3_GRPC list

If your endpoint doesn’t expose reflection, point grpcurl at the proto instead — grpcurl -proto k3_storage.proto -H "Authorization: Bearer $DODIL_TOKEN" $K3_GRPC ....

Field-name casing. gRPC request bodies use the proto field names — snake_case like organization_name, exactly as the .proto blocks in these docs show (grpcurl also accepts camelCase). Responses render in camelCase — that’s grpcurl’s default JSON output. The HTTP surface is camelCase both ways. So within a gRPC example: the request matches the proto block (snake_case), the response is camelCase.

Data-plane adapters

Everything above covers the control plane (api.data.dodil.io / rpc.data.dodil.io). Your data is also reachable over per-protocol wire adapters — stock clients, no K3 SDK — one per wire (Postgres, S3, Bolt, Qdrant, Pinecone, GraphQL, and the Tables HTTP/gRPC doors). The db id is the bucket name on every wire, and each adapter takes the credential the protocol natively carries (API key, service-account client_id:client_secret, or bearer JWT).

The full endpoint table, the per-wire credential matrix, and copy-paste client snippets live on one page — Connect & wire adapters — so they stay in one place rather than drifting across two.

Route Style

  • Bucket-scoped resources use /:bucket/...
  • Admin/global resources use /admin/...
  • Curated AI actions are global, not bucket-scoped: /actions/...
  • IDs are usually opaque strings (source_id, pipeline_id, rule_id, job_id, credential_id, template_id)

Pagination Pattern

List RPCs use cursor pagination:

  • Request: pagination.page_size, pagination.page_token
  • Response: pagination.next_page_token, pagination.total_count

Notes:

  • page_size=0 (or negative) means the server default of 50. The server clamps to a maximum of 200.
  • total_count is an int64, so protobuf JSON renders it as a string, not a number.
  • Only ListBuckets and ListIngestJobs implement pagination today. ListSources, ListRules, and ListPipelines accept the pagination field, ignore it, return the full set, and omit pagination from the response. Their REST forms expose no pagination query parameters at all.
  • ListObjects is the S3-native exception: it paginates with max_keys (default 1000) and continuation_token, not with this pattern.
  • The proto reserves total_count=-1 for “unknown or expensive to compute”, but no service emits it — unknown is signalled by omitting pagination entirely.

Optional Update Semantics

K3 uses wrappers for update requests to avoid ambiguity:

  • StringList and StringMap (both dodil.data.common.v1):
    • field absent: do not change
    • field present but empty: clear existing values
    • field present and non-empty: replace

Over JSON the wrapper’s nested key is requiredvalues for StringList, entries for StringMap:

{ "includePatterns": { "values": ["*.pdf"] } }

replaces the list; { "includePatterns": {} } clears it; omitting the field leaves it alone. A bare {"includePatterns": ["*.pdf"]} will not deserialize. Only two requests use the wrappers: UpdateRule (include_patterns, exclude_patterns, include_mime_types, exclude_mime_types) and UpdatePipeline (options).

For plain optional scalar fields:

  • unset: do not change
  • set (including empty string/zero): update to provided value

Two scalars deviate: UpdateRule.pipeline_id treats "" as no change (the binding is required), and UpdatePipeline.store_entity_id treats "" as clear — it unbinds the pipeline. An update where every field is unset succeeds as a no-op but still bumps updated_at.

Compatibility and Runtime Notes

  1. Canonical vector search route is POST /:bucket/search/vector. The whole legacy /:bucket/vector/... tree — including POST /:bucket/vector/search — is retired.
  2. Object routes in live router are key-in-path style:
    • GET /:bucket/objects/:key/info
    • DELETE /:bucket/objects/:key
    • GET /:bucket/objects/:key/url
  3. Pipelines are created per facet: POST /:bucket/pipelines/vector, /table, /object — and listed with GET /:bucket/pipelines?facet=vector|table|object. The old /:bucket/vector/collections, /:bucket/tables/pipelines, and /:bucket/objects/destinations routes are gone.
  4. GET /:bucket/tables, GET /:bucket/tables/:table_name, and /:bucket/tables/_engine are gone. Table data, DDL, ad-hoc SQL, maintenance, and plane metadata belong to the tables-gateway (Connect & wire adapters); the only /:bucket/tables/... route left on the control plane is /:bucket/tables/reservation.
  5. API-key management lives in IAM (dodil.iam.v1.ApiKeyService), not K3 — the /admin/api-keys REST facade was removed. K3 only verifies keys, at its gateways.
  6. If proto annotation and runtime router differ, treat runtime router as source of truth.

Reusable cURL Template

export DODIL_TOKEN="<bearer_token>" curl -sS "https://api.data.dodil.io/<path>" \ -H "Authorization: Bearer $DODIL_TOKEN" \ -H "Content-Type: application/json"

See also