Skip to content

API Reference

Arca implements 19 S3 operations in its MVP. All operations follow the standard AWS S3 REST API specification.

Authentication

All requests must be signed with AWS Signature V4. Arca supports both the Authorization header and query string authentication methods.

Bucket Operations

Operation Method Path Description
ListBuckets GET / List all buckets
CreateBucket PUT /{bucket} Create a new bucket
HeadBucket HEAD /{bucket} Check if a bucket exists
DeleteBucket DELETE /{bucket} Delete an empty bucket
GetBucketLocation GET /{bucket}?location Get bucket region

Object Operations

Operation Method Path Query Params Description
PutObject PUT /{bucket}/{key+} — Upload an object
GetObject GET /{bucket}/{key+} — Download an object
HeadObject HEAD /{bucket}/{key+} — Get object metadata
DeleteObject DELETE /{bucket}/{key+} — Delete an object
DeleteObjects POST /{bucket} delete Delete multiple objects in one request
CopyObject PUT /{bucket}/{key+} — Copy an object (uses x-amz-copy-source header)

Listing Operations

Operation Method Path Query Params Description
ListObjectsV1 GET /{bucket} — List objects in a bucket (legacy)
ListObjectsV2 GET /{bucket} list-type=2 List objects in a bucket

Multipart Upload Operations

Operation Method Path Query Params Description
CreateMultipartUpload POST /{bucket}/{key+} uploads Initiate a multipart upload
UploadPart PUT /{bucket}/{key+} partNumber, uploadId Upload a part
CompleteMultipartUpload POST /{bucket}/{key+} uploadId Complete a multipart upload
AbortMultipartUpload DELETE /{bucket}/{key+} uploadId Abort a multipart upload

Operation Details

ListBuckets

List all buckets owned by the authenticated user.

GET / HTTP/1.1

Returns ListAllMyBucketsResult XML with bucket names and creation dates.

CreateBucket

Create a new bucket.

PUT /{bucket} HTTP/1.1

Bucket names must follow S3 naming rules: 3-63 characters, lowercase letters, numbers, hyphens, no consecutive periods or IP-like names.

HeadBucket

Check whether a bucket exists and you have permission to access it.

HEAD /{bucket} HTTP/1.1

Returns 200 OK if the bucket exists, 404 Not Found otherwise.

DeleteBucket

Delete a bucket. The bucket must be empty.

DELETE /{bucket} HTTP/1.1

Returns 204 No Content on success, 409 BucketNotEmpty if the bucket contains objects.

GetBucketLocation

Get the region of a bucket.

GET /{bucket}?location HTTP/1.1

Returns a LocationConstraint XML element. Arca always returns an empty location constraint (equivalent to us-east-1).

PutObject

Upload an object. The request body is streamed directly to storage — never buffered in memory.

PUT /{bucket}/{key+} HTTP/1.1
Content-Type: application/octet-stream

ETag is computed as the hex-encoded MD5 of the object content.

The body is verified against Content-MD5, x-amz-checksum-* and a hex x-amz-content-sha256 before the object is stored, see Request body integrity. The checksum (algorithm and value) is recorded on the object and returned as x-amz-checksum-<algo> on this response, and on GetObject / HeadObject when the request carries x-amz-checksum-mode: ENABLED.

GetObject

Download an object. Supports Range header for partial content retrieval.

GET /{bucket}/{key+} HTTP/1.1

Returns the object body with Content-Type, Content-Length, ETag, and Last-Modified headers.

?partNumber={n} returns the bytes of part n of an object assembled by CompleteMultipartUpload (206, with Content-Range and x-amz-mp-parts-count; the ETag is the object's). With x-amz-checksum-mode: ENABLED the response carries the checksum of that part and the object's x-amz-checksum-type. A part number outside the object's parts, or n > 1 on an object that was not uploaded in parts, answers 400 InvalidPart; n = 1 on such an object returns it whole. partNumber cannot be combined with Range (400 InvalidRequest). A multipart object completed by an older version has no recorded part layout, so ?partNumber answers 400 InvalidPart for it.

HeadObject

Retrieve object metadata without downloading the body.

HEAD /{bucket}/{key+} HTTP/1.1

Returns the same headers as GetObject, but with no response body. ?partNumber={n} gives the size (Content-Length) and position (Content-Range) of one part, as above.

DeleteObject

Delete an object.

DELETE /{bucket}/{key+} HTTP/1.1

Returns 204 No Content on success. Deleting a non-existent key is not an error.

Directory Markers

S3 uses zero-byte objects with keys ending in / as directory markers (e.g., photos/). Arca preserves trailing slashes in object keys, so you can create, list, and delete directory markers just like any other object. When listing with a delimiter, directory markers whose key matches the listing prefix are excluded from Contents (they appear only as CommonPrefixes).

DeleteObjects

Delete multiple objects in a single request.

POST /{bucket}?delete HTTP/1.1

<Delete>
  <Quiet>true</Quiet>
  <Object><Key>file1.txt</Key></Object>
  <Object><Key>file2.txt</Key></Object>
  <Object><Key>photos/</Key></Object>
</Delete>

When Quiet is true, the response only includes errors. When false (default), the response includes both successfully deleted keys and errors.

Returns DeleteResult XML. Deleting non-existent keys is not an error.

CopyObject

Copy an object within or across buckets. The source is specified via the x-amz-copy-source header.

PUT /{bucket}/{key+} HTTP/1.1
x-amz-copy-source: /{source-bucket}/{source-key}

Returns CopyObjectResult XML with the new ETag and last modified timestamp.

ListObjectsV1

List objects in a bucket (legacy API). Prefer ListObjectsV2 for new applications.

GET /{bucket} HTTP/1.1
Parameter Description
prefix Limits results to keys beginning with the specified prefix
delimiter Groups keys that share a common prefix (typically /)
max-keys Maximum number of keys to return (default 1000)
marker Start listing after this key

Returns ListBucketResult XML with Contents, CommonPrefixes, IsTruncated, and NextMarker.

ListObjectsV2

List objects in a bucket with optional prefix and delimiter filtering.

GET /{bucket}?list-type=2 HTTP/1.1
Parameter Description
prefix Limits results to keys beginning with the specified prefix
delimiter Groups keys that share a common prefix (typically /)
max-keys Maximum number of keys to return (default 1000)
continuation-token Token from a previous truncated response
start-after Start listing after this key

Returns ListBucketResult XML with Contents, CommonPrefixes, IsTruncated, and NextContinuationToken.

CreateMultipartUpload

Initiate a multipart upload and obtain an upload ID.

POST /{bucket}/{key+}?uploads HTTP/1.1

Returns InitiateMultipartUploadResult XML containing the UploadId.

x-amz-checksum-algorithm (CRC32, CRC32C, CRC64NVME, SHA1, SHA256) and x-amz-checksum-type (COMPOSITE or FULL_OBJECT) choose the checksum the completed object gets, see Multipart checksums. Both are echoed back as response headers. A type alone, an unknown algorithm or type, and a combination that does not exist (SHA with FULL_OBJECT, CRC64NVME with COMPOSITE) are 400 InvalidRequest; without a type the algorithm's default is used.

UploadPart

Upload a part for a multipart upload.

PUT /{bucket}/{key+}?partNumber={n}&uploadId={id} HTTP/1.1

Part numbers range from 1 to 10000. Each part (except the last) must be at least 5 MB.

The part body is verified exactly like a PutObject body (see Request body integrity); a refused part is not stored and does not replace an earlier upload of the same part number. A verified x-amz-checksum-<algo> is returned on the response.

When the upload was created with a checksum algorithm, the part is checksummed with it: Arca computes the checksum when the request declares none, and a checksum declared for another algorithm is 400 InvalidRequest (Checksum Type mismatch occurred, expected checksum Type: ..., actual checksum Type: ...).

CompleteMultipartUpload

Complete a multipart upload by assembling previously uploaded parts.

POST /{bucket}/{key+}?uploadId={id} HTTP/1.1

<CompleteMultipartUpload>
  <Part>
    <PartNumber>1</PartNumber>
    <ETag>"etag1"</ETag>
  </Part>
  <Part>
    <PartNumber>2</PartNumber>
    <ETag>"etag2"</ETag>
  </Part>
</CompleteMultipartUpload>

The resulting ETag is a composite: hex(MD5(binary_MD5(part1) || binary_MD5(part2) || ...))-{count}.

The response carries the object's Checksum<ALGO> and ChecksumType when the upload has a checksum algorithm. See Multipart checksums for what is checked and what the object gets.

AbortMultipartUpload

Abort a multipart upload and delete all uploaded parts.

DELETE /{bucket}/{key+}?uploadId={id} HTTP/1.1

Returns 204 No Content on success.

Conditional writes

PutObject, CompleteMultipartUpload, DeleteObject (including version-specific deletes) and DeleteObjects accept preconditions, so that clients can implement optimistic concurrency (compare-and-swap) on an object:

Operation Headers / fields Fails with
PutObject, CompleteMultipartUpload If-Match: <etag> (or *), If-None-Match: <etag> (or *) 412 PreconditionFailed; 404 NoSuchKey for If-Match when the object does not exist
DeleteObject If-Match, x-amz-if-match-last-modified-time, x-amz-if-match-size 412 PreconditionFailed
DeleteObjects per-key ETag, LastModifiedTime, Size a per-key PreconditionFailed entry in the DeleteResult errors

If-None-Match: * is the "create only if absent" primitive. Deleting a key that does not exist is never a precondition failure (it is a no-op, as for an unconditional delete). A version-specific delete is checked against that exact version, not against the latest one.

Atomicity. The precondition is evaluated inside the same metadata transaction that installs the new version or removes the old one, so two concurrent conditional writes against the same ETag cannot both succeed: exactly one commits, the other gets 412. The check the handler performs before reading the body is only an optimisation that rejects stale requests early. See Architecture › Conditional writes for how each metadata backend guarantees this.

Cost of losing a race. The authoritative check runs at commit time, after the body has been received and stored, so a writer that loses a race has uploaded its whole body for nothing: the blob is deleted and the response is 412. For a refused CompleteMultipartUpload only the assembled object is discarded; the upload and its parts are kept, so the client can retry.

Clusters. In an HA cluster the check is exact per node, not cluster-wide: two conditional writes for the same key served by two different nodes at the same moment can both succeed (TD-025). Route a key's conditional writes to a single node, see High Availability.

Request body integrity

PutObject and UploadPart verify the request body against every digest the request declares, as AWS S3 does. A body that fails is not stored (an existing object or part is left untouched) and the request fails with:

Header Value On mismatch When malformed
Content-MD5 base64 of the 16-byte MD5 400 BadDigest 400 InvalidDigest
x-amz-checksum-crc32, -crc32c, -crc64nvme, -sha1, -sha256 base64 of the big-endian CRC or the digest 400 BadDigest (The CRC32 you specified did not match the calculated checksum.) 400 InvalidRequest
x-amz-trailer: x-amz-checksum-<algo> with an aws-chunked body (STREAMING-UNSIGNED-PAYLOAD-TRAILER, STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER) the same value, sent as a trailing header after the last chunk 400 BadDigest 400 InvalidRequest, also when the declared trailer is missing
x-amz-content-sha256 64 hex digits 400 XAmzContentSHA256Mismatch — (UNSIGNED-PAYLOAD and STREAMING-* carry no digest and are not checked)

At most one checksum algorithm per request: two x-amz-checksum-* values (or a header and a trailer) are rejected with 400 InvalidRequest. Malformed values are refused before the body is read.

This is what makes the default behaviour of current SDKs work end to end: boto3 / botocore 1.36+ send a CRC32 on every upload (a header over HTTP, an aws-chunked trailer over HTTPS) and the AWS CLI a CRC64NVME; MinIO clients send Content-MD5, a hex x-amz-content-sha256 or a CRC32C. The checksum is stored and returned with x-amz-checksum-mode: ENABLED.

The chunks and the trailer of a signed aws-chunked body (STREAMING-AWS4-HMAC-SHA256-PAYLOAD and -PAYLOAD-TRAILER, sent by MinIO clients over plain HTTP) are verified against the request's signature while the body streams: a modified byte, a bad or missing chunk signature, a bad trailer signature and a body cut before its final chunk all answer 403 SignatureDoesNotMatch and store nothing. Signed chunks are not accepted with a presigned URL.

Multipart checksums

An upload created with x-amz-checksum-algorithm gives its completed object a checksum, built from the part checksums (no object byte is read at Complete time):

Type Value Algorithms
COMPOSITE base64(H(raw part checksums, concatenated))-N, N the number of parts CRC32, CRC32C, SHA1, SHA256
FULL_OBJECT the CRC of the whole object, rebuilt from the part CRCs and sizes CRC32, CRC32C, CRC64NVME

CRC64NVME (the AWS CLI default) only has the full-object form, SHA1 and SHA256 only the composite one; CRC32 and CRC32C default to composite.

CompleteMultipartUpload checks, before anything is assembled (a refused request leaves the upload as it was, so the client can fix it and complete again):

  • x-amz-mp-object-size, when sent, equals the size of the object (400 InvalidRequest);
  • x-amz-checksum-type, when sent, is the upload's type (400 InvalidRequest);
  • a whole-object x-amz-checksum-<algo> header equals the checksum computed from the parts (400 BadDigest; one for another algorithm is 400 InvalidRequest);
  • a Checksum<ALGO> given for a part in the XML equals the one stored for that part (400 BadDigest); a part left without one in the XML is accepted.

The object gets a checksum only when every part has a stored checksum; a part uploaded by an older version may not. The checksum is returned by Complete, by HeadObject and GetObject with x-amz-checksum-mode: ENABLED, and by GetObjectAttributes (Checksum, with ChecksumType).

The layout of the parts (number, size and checksum of each) is recorded with the object for every multipart object, with or without a checksum. It is what ?partNumber and the ObjectParts of GetObjectAttributes read (x-amz-max-parts and x-amz-part-number-marker page it). It is read on demand, so GET, HEAD and listings of a multipart object cost the same whatever its number of parts.

CopyObject keeps a FULL_OBJECT checksum (same bytes) and drops a COMPOSITE one: the copy has no parts, and never has a layout.

Intentional divergences from AWS S3

Arca targets drop-in AWS S3 compatibility: what AWS accepts, Arca accepts; what AWS rejects, Arca rejects — with the following deliberate, documented exceptions.

Conditional CompleteMultipartUpload never returns 409

AWS can answer a conditional CompleteMultipartUpload with 409 ConditionalRequestConflict when a conflicting operation is in flight. Arca evaluates the precondition once, at commit time, and has no "conflict in flight" state, so every refused precondition is 412 PreconditionFailed. Clients that retry on 409 should treat 412 the same way: re-read the object and decide again.

Multipart uploads without a checksum algorithm

AWS now gives an object uploaded without any checksum a CRC64NVME FULL_OBJECT checksum, computed by the service. Arca does not: an upload created without x-amz-checksum-algorithm completes without an object checksum, as before. Clients that ask for a checksum (the AWS CLI and current SDKs do by default) get it.

If-None-Match with a specific ETag

AWS documents If-None-Match on writes only with the value *. Arca also accepts a specific ETag (or a list), with standard HTTP semantics: the write proceeds unless the current object's ETag matches. Code that relies on this is not portable to AWS.

Object Lock on existing empty buckets

AWS only allows Object Lock to be enabled at bucket creation (x-amz-bucket-object-lock-enabled: true); PutObjectLockConfiguration on a bucket that was not created with Object Lock returns 409 InvalidBucketState.

Arca additionally accepts PutObjectLockConfiguration on an existing bucket that is still empty (auto-enabling versioning, as AWS does at creation). A late enable on an empty bucket cannot retroactively weaken WORM protection on any object, so it is safe; non-empty buckets are still rejected with 409 InvalidBucketState. This is a console-UX convenience: enable Object Lock from a bucket's settings page without recreating the bucket.

Implication: the drop-in direction (AWS-targeted clients running against Arca) is unaffected, but code that relies on this relaxation is not portable to AWS, which would reject it. This divergence is also why the Ceph s3-test test_object_lock_put_obj_lock_invalid_bucket is expected to fail.