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
Authorizationheader — 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 verifiesAuthorization: Beareras a Keycloak JWT only — adk_…value sent as the Bearer here fails with401 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 onceUsing 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 listIf 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_countis anint64, so protobuf JSON renders it as a string, not a number.- Only
ListBucketsandListIngestJobsimplement pagination today.ListSources,ListRules, andListPipelinesaccept thepaginationfield, ignore it, return the full set, and omitpaginationfrom the response. Their REST forms expose no pagination query parameters at all. ListObjectsis the S3-native exception: it paginates withmax_keys(default 1000) andcontinuation_token, not with this pattern.- The proto reserves
total_count=-1for “unknown or expensive to compute”, but no service emits it — unknown is signalled by omittingpaginationentirely.
Optional Update Semantics
K3 uses wrappers for update requests to avoid ambiguity:
StringListandStringMap(bothdodil.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 required — values 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
- Canonical vector search route is
POST /:bucket/search/vector. The whole legacy/:bucket/vector/...tree — includingPOST /:bucket/vector/search— is retired. - Object routes in live router are key-in-path style:
GET /:bucket/objects/:key/infoDELETE /:bucket/objects/:keyGET /:bucket/objects/:key/url
- Pipelines are created per facet:
POST /:bucket/pipelines/vector,/table,/object— and listed withGET /:bucket/pipelines?facet=vector|table|object. The old/:bucket/vector/collections,/:bucket/tables/pipelines, and/:bucket/objects/destinationsroutes are gone. GET /:bucket/tables,GET /:bucket/tables/:table_name, and/:bucket/tables/_engineare 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.- API-key management lives in IAM (
dodil.iam.v1.ApiKeyService), not K3 — the/admin/api-keysREST facade was removed. K3 only verifies keys, at its gateways. - 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"