S3 Compatibility
K3’s HTTP gateway speaks native S3 on the byte plane. Point any S3 SDK at it with your Dodil service-account credentials and existing code works — aws s3 sync, boto3.client('s3'), @aws-sdk/client-s3, MinIO mc — no code changes beyond the endpoint URL.
What this page covers: credentials, supported operations, auth modes, and end-to-end setup for the four most common clients.
Credentials & endpoint
Single source of credentials: your Dodil service account. The same <service_account_id> / <service_account_secret> pair you pass to dodil login doubles as your S3 access-key / secret-key — both are issued by Dodil IAM.
| Value | |
|---|---|
| Endpoint (staging) | https://k3.dev.dodil.io |
| Endpoint (production) | https://k3.dodil.io |
| Access key ID | your Dodil <service_account_id> |
| Secret access key | your Dodil <service_account_secret> |
| Region | any string (e.g. us-east-1) — must be consistent client-side, K3 derives the signing key from whatever scope you send |
| Addressing style | path-style only — virtual-host style (<bucket>.k3.dev.dodil.io) is not routed |
Bearer JWT also works on the byte plane (see Auth modes below) if you don’t have SigV4 credentials handy — but SigV4 is what every standard S3 SDK speaks.
Setup — pick your client
aws-cli
# One-time configuration
aws configure --profile dodil-k3
# AWS Access Key ID: <service_account_id>
# AWS Secret Access Key: <service_account_secret>
# Default region name: us-east-1
# Default output format: json
# Test
aws s3 ls \
--endpoint-url https://k3.dev.dodil.io \
--profile dodil-k3Drop the per-command flags by exporting them once:
export AWS_PROFILE=dodil-k3
export AWS_ENDPOINT_URL=https://k3.dev.dodil.io # aws-cli v2.15+
aws s3 ls
aws s3 cp ./hello.txt s3://kb-prod/greetings/hello.txtWhat’s supported
K3 implements the S3 surface needed for production object storage and ingest. Anything not in the tables below is out of scope — K3 does not currently support object versioning, lifecycle / expiration policies, replication, SQS/SNS event notifications, customer-managed encryption keys, or object lock.
Object operations
| S3 action | Wire shape | Supported | Notes |
|---|---|---|---|
PutObject | PUT /:bucket/:key | ✅ | Triggers ingest discovery on success — see Pipelines |
GetObject | GET /:bucket/:key | ✅ | Range header supported |
HeadObject | HEAD /:bucket/:key | ✅ | |
DeleteObject | DELETE /:bucket/:key | ✅ | Cleans up downstream pipeline indexes |
DeleteObjects (bulk) | POST /:bucket?delete | ✅ | |
ListObjectsV2 | GET /:bucket?list-type=2 | ✅ | prefix / delimiter / continuation-token / max-keys |
CreateMultipartUpload | POST /:bucket/:key?uploads | ✅ | |
UploadPart | PUT /:bucket/:key?partNumber&uploadId | ✅ | |
CompleteMultipartUpload | POST /:bucket/:key?uploadId | ✅ | Triggers ingest discovery |
AbortMultipartUpload | DELETE /:bucket/:key?uploadId | ✅ | |
ListMultipartUploads | GET /:bucket?uploads | ✅ |
Bucket operations
| S3 action | Wire shape | Supported | Notes |
|---|---|---|---|
CreateBucket | PUT /:bucket | ✅ | Equivalent to CreateBucket admin RPC |
DeleteBucket | DELETE /:bucket | ✅ | Bucket must be empty |
ListBuckets | GET / | ✅ | |
HeadBucket | HEAD /:bucket | ✅ | |
PutBucketPolicy / GetBucketPolicy / DeleteBucketPolicy | `PUT | GET | DELETE /:bucket?policy` |
PutBucketCors / GetBucketCors / DeleteBucketCors | `PUT | GET | DELETE /:bucket?cors` |
Not supported
| S3 action | Why not | Workaround |
|---|---|---|
PutBucketVersioning / object versions | Not in K3’s data model | Use explicit key versioning in object paths if needed |
PutBucketLifecycleConfiguration | No lifecycle engine | Roll your own expiration via Pipelines or scheduled cleanup |
PutBucketNotificationConfiguration (SQS/SNS) | Use K3’s native ingest plane | Define pipeline rules — every upload fires through them automatically |
PutBucketReplication | Single-region today | — |
PutBucketEncryption (SSE-C, SSE-KMS w/ customer keys) | All buckets are encrypted at rest at the backend level | Customer-managed keys are roadmap |
PutObjectAcl / GetObjectAcl (per-object ACL) | K3 ACL is per-bucket | Use Bucket Policy — same expressive power for S3 use cases |
PutObjectLockConfiguration / retention / legal-hold | Not implemented | — |
| Bucket / object tagging | Forwarded to backend, not validated | Don’t rely on it for production logic |
Auth modes accepted on the byte plane
| Mode | Header / query shape | When to use |
|---|---|---|
| SigV4 header | Authorization: AWS4-HMAC-SHA256 Credential=…/yyyymmdd/<region>/s3/aws4_request,SignedHeaders=…,Signature=… | Standard S3 SDKs — what every aws s3 / boto3 / @aws-sdk request sends |
| SigV4 query | ?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=… | SDK-issued presigned URLs (aws s3 presign, generate_presigned_url, getSignedUrl) |
| SigV2 query | ?AWSAccessKeyId=&Signature=&Expires= | Legacy clients that still use SigV2 presigning |
| Bearer JWT | Authorization: Bearer <token> | Server-to-server when you already have a Dodil JWT — avoids the SigV4 dance |
| K3 token query | ?X-K3-Token=&X-K3-Expires=&X-K3-Org= | URLs issued by GetObjectUrl — recommended for app-issued share links |
Region from the credential scope (…/<yyyymmdd>/<region>/s3/aws4_request) is whatever the client picks — K3 derives the signing key from it, so any consistent region works.
End-to-end: aws-cli ↔ dodil k3
Objects uploaded via raw S3 are first-class K3 objects — admin RPCs see them, pipelines fire on them, presigned URLs work over them.
# 1. Create a bucket via S3 (equivalent to `dodil k3 bucket create`)
aws s3 mb s3://kb-s3-demo
# 2. Upload an object
echo "hello from aws-cli" > hello.txt
aws s3 cp hello.txt s3://kb-s3-demo/greetings/hello.txt
# 3. Inspect via K3 admin — same object, enriched with ingest status
dodil k3 object show greetings/hello.txt -b kb-s3-demo -o json
# 4. Issue a K3-signed share URL (shorter than SigV4 presign)
dodil k3 object url greetings/hello.txt -b kb-s3-demo --expires 3600The takeaway: S3 in, K3 features out. You don’t have to choose a side — buckets are interoperable.
Error response shape
S3-style errors come back as XML on the byte plane (compatible with every S3 SDK), JSON envelope on the admin plane. The two most common byte-plane errors:
<!-- 403 — storage quota exhausted, ACL denial, or auth failure -->
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>AccessDenied</Code>
<Message>access denied</Message>
</Error><!-- 404 — bucket or key not found -->
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>NoSuchKey</Code>
<Message>The specified key does not exist.</Message>
</Error>Standard SDKs surface these as typed exceptions (ClientError in boto3, S3ServiceException in the JS SDK). For admin RPCs (dodil k3 ... / gRPC), see Conventions for the JSON error envelope.
Next steps
- Recipes — direct-from-browser upload, multipart for large files, static-site hosting
- API Reference — gRPC + HTTP admin contracts
- Pipelines — auto-indexing on upload (the K3-specific feature S3 doesn’t have)
- Auth and Access — auth-mode internals