Credentials — API Reference
Package: dodil.data.source.v1 · Service: SourceService
Credentials are org-scoped (not bucket-scoped) and may optionally link to a source_id. Five credential types, discriminated by CredentialType and a oneof credential_data payload — see Core Concepts → Credential.
Preview — the entire Credentials API surface, including the OAuth flow, is paired with external sources, which are Preview. The production internal-S3 path requires no credential. Shapes below reflect today’s API; expect refinements.
| RPC | HTTP |
|---|---|
StoreCredential | POST /admin/credentials |
ListCredentials | GET /admin/credentials |
DeleteCredential | DELETE /admin/credentials/:credential_id |
GetOAuthUrl | POST /admin/oauth/authorize |
ExchangeOAuthCode | POST /admin/oauth/token |
RefreshOAuthToken | POST /admin/credentials/:credential_id/refresh |
Secrets are stored in HashiCorp Vault behind K3, and there is no way to read one back out.
ListCredentialsreturnsCredentialInfoonly — never a secret payload./admin/credentials/:credential_idacceptsDELETEand nothing else.
Two retired RPCs
Both were removed from SourceService, and neither has a route in the router. Calls fail — do not build against them.
| Retired RPC | Why it went |
|---|---|
GetCredential | It returned raw access/refresh tokens, API keys, service-account JSON and PATs — at VIEWER tier. A control plane whose job is to hold secrets in Vault must not offer an org-level RPC that reads them back in plaintext. There is no replacement, and none is planned: nothing legitimate needed it. |
ValidateCredential | It validated nothing. It echoed the stored is_valid — which is only ever written true — and stamped validated_at: now(), implying a live provider check it never performed. A constant-true function that reports freshness is worse than an absent one. A real probe would be a new RPC that actually calls the provider. |
Because GetCredential is gone, there is no auto_refresh shortcut. Refresh expiring OAuth tokens explicitly with RefreshOAuthToken.
gRPC setup —
grpcurl, endpoints, reflection, and field-name casing — is covered once in Conventions → Using gRPC.
StoreCredential
Stores a credential directly. For OAuth providers, prefer the OAuth flow below — it handles redirect + code exchange end-to-end. Use this for API keys, PATs, service-account JSON keys, and access keys.
Request
HTTP
GitHub PAT variant:
curl -sS -X POST "https://api.data.dodil.io/admin/credentials" \
-H "Authorization: Bearer $DODIL_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "SOURCE_PROVIDER_GITHUB",
"credentialType": "CREDENTIAL_TYPE_PAT",
"displayName": "github-readonly",
"isPrimary": true,
"pat": { "token": "ghp_...", "username": "dodil-bot" }
}'External S3 access-key variant:
curl -sS -X POST "https://api.data.dodil.io/admin/credentials" \
-H "Authorization: Bearer $DODIL_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "SOURCE_PROVIDER_INTERNAL_S3",
"credentialType": "CREDENTIAL_TYPE_ACCESS_KEY",
"displayName": "external-s3",
"accessKey": {
"accessKeyId": "AKIA...",
"secretAccessKey": "wJalrXUt...",
"region": "us-east-1",
"endpoint": "https://s3.example.com"
}
}'Response
HTTP
{
"credentialId": "cred_a1b2..."
}ListCredentials
Returns CredentialInfo rows only — no secret payloads. Filter by source_id or provider. This is the only read path for credentials.
isValidon the response is not a health signal. It is only ever writtentrueat store time and nothing ever revalidates it. Treat it as “a credential row exists”, not “the provider still accepts this credential” — the RPC that claimed to check that (ValidateCredential) was retired precisely because it never did. To find out whether a credential still works, run a sync and read the source’slast_errorviaGetSyncStatus.
Request
HTTP
All credentials for Google Drive — provider is the enum’s numeric value over HTTP, not its wire name:
curl -sS "https://api.data.dodil.io/admin/credentials?provider=10" \
-H "Authorization: Bearer $DODIL_TOKEN"All credentials linked to a specific source:
curl -sS "https://api.data.dodil.io/admin/credentials?source_id=src_a1b2..." \
-H "Authorization: Bearer $DODIL_TOKEN"The handler deserializes provider as an integer, so ?provider=SOURCE_PROVIDER_GOOGLE_DRIVE is rejected. The numbers are not contiguous:
| Provider | ?provider= |
|---|---|
SOURCE_PROVIDER_INTERNAL_S3 | 1 |
SOURCE_PROVIDER_GOOGLE_DRIVE | 10 |
SOURCE_PROVIDER_SHAREPOINT | 11 |
SOURCE_PROVIDER_CONFLUENCE | 12 |
SOURCE_PROVIDER_GITHUB | 20 |
gRPC callers pass the wire name as usual.
Response
HTTP
{
"credentials": [
{
"credentialId": "cred_a1b2...",
"sourceId": "src_a1b2...",
"provider": "SOURCE_PROVIDER_GOOGLE_DRIVE",
"credentialType": "CREDENTIAL_TYPE_OAUTH2",
"displayName": "gdrive-main",
"isPrimary": true,
"isValid": true,
"createdAt": "1716840000000",
"updatedAt": "1716843600000",
"expiresAt": "1716847200000"
}
]
}DeleteCredential
Request
HTTP
curl -sS -X DELETE "https://api.data.dodil.io/admin/credentials/cred_a1b2..." \
-H "Authorization: Bearer $DODIL_TOKEN"Response
Empty (DeleteCredentialResponse {}).
OAuth flow
Three RPCs, one per stage of the standard OAuth authorization-code flow:
GetOAuthUrl— your backend asks K3 for an authorization URL; K3 returns the URL with the right scopes + client ID for the provider.- User redirect — your frontend redirects the user to that URL; the user authorizes; the provider redirects back to your
redirect_uriwith?code=...&state=.... ExchangeOAuthCode— your backend hands the code back to K3, which exchanges it for tokens and stores them as a credential. Returns thecredential_id.
Later, RefreshOAuthToken refreshes expiring tokens. This is the only refresh path — the implicit auto_refresh shortcut went with GetCredential.
GetOAuthUrl
Request
HTTP
curl -sS -X POST "https://api.data.dodil.io/admin/oauth/authorize" \
-H "Authorization: Bearer $DODIL_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "SOURCE_PROVIDER_GOOGLE_DRIVE",
"redirectUri": "https://app.example.com/k3/oauth/callback",
"scopes": ["https://www.googleapis.com/auth/drive.readonly"],
"state": "req-123"
}'Response
HTTP
{
"authUrl": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...",
"state": "req-123"
}ExchangeOAuthCode
Request
HTTP
curl -sS -X POST "https://api.data.dodil.io/admin/oauth/token" \
-H "Authorization: Bearer $DODIL_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "SOURCE_PROVIDER_GOOGLE_DRIVE",
"code": "<oauth_code>",
"redirectUri": "https://app.example.com/k3/oauth/callback",
"state": "req-123",
"displayName": "gdrive-main"
}'Response
HTTP
{
"credentialId": "cred_a1b2..."
}RefreshOAuthToken
Request
HTTP
curl -sS -X POST "https://api.data.dodil.io/admin/credentials/cred_a1b2.../refresh" \
-H "Authorization: Bearer $DODIL_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"credentialId": "cred_a1b2..."
}'Response
HTTP
{
"success": true,
"expiresAt": "1716847200000"
}See also
- Sources — link credentials to sources via
source_id - Core Concepts → Credential — the typed payload variants
- CLI Guide → credential —
dodil data credential store / oauth-url / oauth-exchange grpcurlreference — full flag set + reflection-disabled fallbacks