Documentation

Proxy error responses

Unlike the dashboard API (JSON), the proxy returns AWS-shaped XML so SDKs treat it as a normal S3 error.

#Wire shape

<?xml version="1.0" encoding="UTF-8"?>
<Error>
  <Code>AccessDenied</Code>
  <Message>credential not authorised for bucket</Message>
  <BucketName>my-bucket</BucketName>
  <Resource>/my-bucket/path/to/object</Resource>
  <RequestId>17b1c8a3</RequestId>
</Error>

Compatible with every AWS SDK error parser.

#Common codes the proxy emits

HTTPCodeCause
400InvalidRequestOperation classification failed; request shape unrecognised.
400MalformedXMLBody parse failure on DeleteObjects / CompleteMultipartUpload.
401InvalidAccessKeyIdThe access key is unknown.
401SignatureDoesNotMatchThe signature didn't verify. Check secret + clock skew.
403AccessDeniedScope violation, anonymous-disallowed operation, expired credential.
403RequestTimeTooSkewedDate in the request is outside the verifier's tolerance.
404NoSuchBucketThe bucket isn't known to the upstream.
404NoSuchKeyObject doesn't exist (passed through from upstream).
411MissingContentLengthA streaming PUT didn't carry a length header.
429SlowDownPer-credential, global, or per-source-IP rate limit. Retry-After header.
500InternalErrorUnrecoverable proxy or upstream failure.
502BadGatewayUpstream returned an unparseable response.
503ServiceUnavailableUpstream temporarily unavailable; transient.
507EntityTooLargeBucket hard-cap exceeded. Do not retry blindly. See Quota errors.

#Auth-failure reasons (in metrics)

stowage_s3_auth_failure_total is labeled by reason. Non-exhaustive:

reasonMeaning
unknown_access_keyThe access key isn't in the cache.
bad_signatureStringToSign didn't match.
disabledCredential is in the cache but flagged disabled.
expiredCredential carries an ExpiresAt and it's past.
bad_authorization_formatThe header didn't parse as SigV4.
bad_credential_fieldThe Credential= field couldn't be split.
bad_dateThe X-Amz-Date is malformed.
time_skewDate is outside the allowed skew window.

These map onto a 401 InvalidAccessKeyId or SignatureDoesNotMatch response depending on the cause.

#Scope-violation result

stowage_s3_scope_violation_total increments any time an authenticated request is rejected because the bucket is not in the credential's scope. The wire response is 403 AccessDenied.

#Anonymous-reject reasons

stowage_s3_anonymous_reject_total{reason} is labeled with:

reasonMeaning
disabled_globallys3_proxy.anonymous_enabled: false.
no_bindingThe bucket has no anonymous binding.
operation_not_allowedOperation is outside the read-only allowlist.
rate_limitedPer-IP RPS exceeded.