Use Cloudflare R2
capability_id: r2.use
display_name: Use Cloudflare R2
scope: r2:use
endpoint:
method: POST
path: /r2/presign
operation: r2.presign
billable: true
commands:
- GetObject
- PutObject
environment_selector: none
Purpose and authorization
The r2:use scope authorizes an authenticated application to request a presigned Cloudflare R2 GetObject or PutObject operation. The application must use Contract v1. The documentation identifier r2.use and runtime scope r2:use refer to the same capability at different layers.
Gateman uses the application's configured Cloudflare R2 credentials to sign a native S3-compatible request and returns the URL, method, and headers needed for the application to call R2 directly. Gateman does not return the secret access key, transfer object content, execute the subsequent GET or PUT, check bucket or object existence, register buckets, enforce key prefixes, or add object-level authorization. Cloudflare credential permissions are the provider-side resource and action authorization boundary.
Implementation contract
The outer request is authenticated with Contract v1:
POST /r2/presign
Content-Type: application/json
X-GM-App: app_...
X-GM-Ts: 1710000000
X-GM-Sig: <Contract-v1-signature>
X-GM-Idempotency-Key: optional-gateman-key
X-GM-Idempotency-Key is optional and participates in Contract v1 signing. It does not create an R2-specific replay ledger or deduplicate presigning operations.
The JSON body has this exact top-level contract:
| Field | Required | Contract |
|---|---|---|
operation | Yes | get or put; selects native GetObject or PutObject. |
bucket | Yes | Lowercase R2 bucket name, 3–63 bytes, beginning and ending with a letter or digit and containing only lowercase letters, digits, and hyphens. |
key | Yes | Nonempty UTF-8 object key, at most 1,024 bytes, without control characters. The key remains opaque to Gateman. |
jurisdiction | Yes | One of default, eu, us, fedramp, or fedramp-high. |
expires | Yes | Integer seconds from 1 through 3,600. |
parameters | No | JSON object containing native SDK inputs for the selected operation. |
content_type | No | Legacy PUT compatibility field equivalent to native ContentType. |
Unknown top-level fields are rejected. The caller cannot provide an endpoint, host, region, service, credential, or replacement header configuration.
Native parameters
parameters is validated as an object against the installed AWS SDK operation model. It is not an unchecked JSON bag or a separate exhaustive Gateman allowlist. Native names and value types are checked for the selected GetObject or PutObject operation before operation admission.
- The outer
bucketandkeybecome the trusted SDKBucketandKeyinputs. - Nested
Bucket,Key, andBodyvalues are rejected. - Unknown native parameter names and SDK-invalid value types are rejected.
ChecksumAlgorithmis rejected because Gateman never receives an object body from which to calculate the requested checksum.- Caller-supplied explicit checksum values may be accepted and signed when the installed SDK can serialize them. This does not claim that Cloudflare accepts every AWS checksum algorithm.
- Native fields can become signed query parameters, request headers, or both. The caller must use the returned request exactly.
For PUT, the preferred form is native parameters.ContentType. The legacy top-level content_type remains supported. Matching values may be supplied in both forms; conflicting values return HTTP 400. content_type is rejected for GET. Native GET response-content parameters remain available through parameters.
GET and PUT examples
Native GET
{
"operation": "get",
"bucket": "example-bucket",
"key": "documents/report.pdf",
"jurisdiction": "eu",
"expires": 300,
"parameters": {
"Range": "bytes=0-1048575",
"IfMatch": "\"example-etag\""
}
}
Depending on the native fields, the completed request can contain signed query parameters and required request headers for range retrieval, conditional retrieval, or response overrides. Send the returned method, URL, and every returned header to R2. The application receives and interprets the actual R2 object response directly.
Native PUT
{
"operation": "put",
"bucket": "example-bucket",
"key": "uploads/photo.jpg",
"jurisdiction": "eu",
"expires": 900,
"parameters": {
"ContentType": "image/jpeg",
"Metadata": {
"source": "website"
},
"CacheControl": "max-age=3600",
"StorageClass": "STANDARD"
}
}
The application sends the object bytes directly to R2 using the returned PUT URL and headers. Gateman neither receives the bytes nor verifies that R2 stored them.
Response contract and signed headers
A successful response is HTTP 200:
{
"ok": true,
"url": "https://example-bucket.<account>.eu.r2.cloudflarestorage.com/uploads/photo.jpg?...",
"method": "PUT",
"expires_at": "2026-10-16T12:15:00+00:00",
"headers": {
"Content-Type": "image/jpeg",
"x-amz-meta-source": "website"
}
}
url is the complete presigned URL, method is GET or PUT, and expires_at is the server-calculated UTC expiry. headers contains the non-Host headers in the completed signed request. Every returned header must be sent with the returned method and URL. Do not reconstruct or filter the headers using X-Amz-SignedHeaders; some native parameters become query fields and others become required headers.
The URL is a bearer-like capability until it expires. Protect it from unnecessary disclosure, and do not alter its path encoding or query string. Gateman does not revoke individual URLs or retain an R2 issuance ledger.
ContentLength can be included as a native PUT parameter. The current signer returns a signed Content-Length header, so the value is covered by the local SigV4 signature. This is not an independent Gateman upload-size policy and does not prove Cloudflare's runtime enforcement.
Cloudflare endpoint, jurisdiction, and credentials
Gateman derives the endpoint from the application's configured Cloudflare account ID and the requested jurisdiction:
default:https://<account-id>.r2.cloudflarestorage.com- other accepted values:
https://<account-id>.<jurisdiction>.r2.cloudflarestorage.com
The accepted Gateman jurisdiction values are default, eu, us, fedramp, and fedramp-high. The SigV4 service is s3 and the signing region is always auto; this is not AWS regional selection. The caller cannot replace the account ID, endpoint hostname, credentials, service, or signing region.
Gateman stores and retrieves these application-scoped encrypted secrets:
CLOUDFLARE_R2_ACCESS_KEY_IDCLOUDFLARE_R2_SECRET_ACCESS_KEY
Using /r2/presign does not require secrets:read and does not require the Cloudflare R2 Allow app retrieval setting. Direct credential retrieval is a separate capability and authorization path. Cloudflare's R2 token or access key permissions determine whether the eventual bucket, object, and action are allowed; a successful presign response does not guarantee that R2 will accept the later request.
The capability is environment-independent. It has no environment field, does not select staging or development credentials, and does not use Official client environment injection.
fedramp-high is accepted by the Gateman contract and is used to derive the jurisdiction-specific endpoint. Provider-specific endpoint availability and acceptance remain Cloudflare concerns; this reference does not assert an additional provider guarantee.
Cloudflare provider compatibility
Three boundaries must remain distinct:
- The installed AWS SDK model recognizes a native parameter name and value.
- Gateman can serialize and sign that native request locally.
- Cloudflare R2 accepts and executes the corresponding feature.
Cloudflare documents an S3-compatible API, but it does not implement every AWS S3 feature. The following are useful implementation distinctions:
| Feature | Documentation treatment |
|---|---|
PUT content headers such as Content-Type, cache control, disposition, encoding, language, and explicit content MD5 | Supported by Cloudflare where documented. |
User-defined Metadata | Supported; SDK metadata becomes x-amz-meta-* headers. |
| Range and conditional GET headers | Supported by Cloudflare's S3 API. |
STANDARD and STANDARD_IA storage classes | Supported Cloudflare R2 storage classes. |
PUT Tagging | Cloudflare documents object tagging as unsupported; do not use it as a primary R2 success example. Gateman does not add a provider-specific static rejection. |
| AWS ACLs and object-lock features | Cloudflare documents these AWS features as unsupported. Gateman does not add a provider-specific static rejection. |
| AWS SSE/KMS options | Cloudflare compatibility differs from AWS; do not infer support from SDK recognition. |
| SSE-C | Cloudflare documents support. |
| Response overrides and object-version parameters | Do not claim runtime support without current Cloudflare evidence. |
See Cloudflare S3 API compatibility, presigned URLs, and S3 API extensions. A successful Gateman response means that the request was locally signed, not that Cloudflare accepted or executed every SDK-valid feature.
Body, checksums, and operation accounting
parameters.Body is rejected. Object content must bypass Gateman and go directly from the application to R2. Gateman does not buffer, hash, forward, or inspect the object body.
parameters.ChecksumAlgorithm is rejected because the body is unavailable for checksum calculation. Explicit caller-supplied checksum values may be signed where the SDK supports them, but Gateman does not calculate or verify object checksums.
The billable operation is r2.presign. One operation is consumed per admitted invocation. Validation failures rejected before admission do not consume an operation. Credential, serialization, signing, or other failures discovered after admission remain failed billable operations. The later direct R2 GET or PUT consumes no additional Gateman operation.
Repeated admitted presign requests remain separate operations even when the optional X-GM-Idempotency-Key is reused. That header is Contract v1 input, not an R2 replay ledger.
Monthly allowance exhaustion returns HTTP 403 with monthly_operation_limit_reached. Application concurrency rejection returns HTTP 429 with Retry-After when applicable. An unavailable application concurrency guard returns HTTP 503. Operation-accounting unavailability returns HTTP 503 with Operation accounting unavailable.
Errors
| Condition | HTTP response | Operation consequence |
|---|---|---|
Contract v1 authentication failure or missing r2:use | 401 | Rejected before capability execution; no operation. |
| Invalid JSON | 400 | Rejected before admission; no operation. |
| Missing field, unsupported operation, invalid bucket/key/jurisdiction/expiry | 400 | Rejected before admission; no operation. |
Invalid parameters shape | 400 | Rejected before admission; no operation. |
| Unknown top-level or native parameter | 400 | Rejected before admission; no operation. |
Nested Bucket, Key, or Body | 400 | Rejected before admission; no operation. |
Rejected ChecksumAlgorithm or invalid native value type | 400 | Rejected before admission; no operation. |
GET content_type or conflicting Content-Type values | 400 | Rejected before admission; no operation. |
| Application concurrency rejection | 429, with Retry-After when applicable | No operation. |
| Application concurrency guard unavailable | 503 | No operation. |
| Monthly operation allowance exhausted | 403 | No new operation is reserved. |
| Operation accounting unavailable | 503 | No confirmed operation is reserved. |
| Missing R2 configuration | 400 | Admitted operation is finalized as failed. |
| Missing R2 credentials or credential-store failure | 502 | Admitted operation is finalized as failed. |
Post-admission SDK signing failure classified as InvalidArgumentException | 400 | Admitted operation is finalized as failed. |
| Other unexpected presigning failure | 502 with R2 presigning unavailable | Admitted operation is finalized as failed. |
These are Gateman presigning errors. They are distinct from an R2 response returned later when the application uses the URL, such as an authorization denial, expired signature, missing object, unsupported provider feature, or invalid request header. HTTP status alone does not determine whether a billable operation was consumed.
Implementation outcome
A correct integration:
- authenticates with Contract v1 and the
r2:usescope; - calls
POST /r2/presignwithgetorput, bucket, full opaque key, jurisdiction, and expiry; - supplies native SDK parameters only when needed and respects reserved fields;
- preserves the returned URL, method, and every returned header;
- executes the signed request directly against Cloudflare R2;
- interprets the actual Cloudflare response and provider permissions; and
- accounts for one
r2.presignoperation per admitted Gateman invocation.
Implementation instructions for agents
- MUST authenticate with Contract v1, request the
r2:usescope, and callPOST /r2/presign. - MUST send
operationasgetorput, withbucket, fullkey, supportedjurisdiction, and integerexpiresfrom 1 through 3,600. - MAY send native
GetObjectorPutObjectinputs in theparametersobject. Do not treat it as unchecked JSON. - MUST NOT send nested
Bucket,Key, orBody; object bytes never pass through Gateman. MUST NOT sendChecksumAlgorithm; Gateman rejects it because object content never passes through Gateman. - MAY use legacy PUT
content_type, but MUST NOT use it for GET or conflict it withparameters.ContentType. - MUST send every returned header with the returned URL and method. Do not filter or reconstruct headers from
X-Amz-SignedHeaders. - MUST use the configured Cloudflare account context, service
s3, and signing regionauto; callers cannot override them. - MUST treat Cloudflare credentials and permissions as authoritative for buckets, keys, and provider actions.
- MUST distinguish local SDK signing success from Cloudflare provider compatibility and execution success.
- MUST account for each admitted
r2.presigncall and must not invent bucket registration, prefix restrictions, object authorization, retries, or R2-specific Official client helpers.
Reusable AGENTS.md block
<!-- gateman:begin capability=r2.use -->
- Use Contract v1 with the `r2:use` scope and target `POST /r2/presign`.
- Send `operation` as `get` or `put`, plus the caller-selected `bucket`, full
opaque `key`, supported `jurisdiction`, and integer `expires` from 1 through
3,600. R2 has no `environment` selector; signing uses service `s3` and
region `auto`.
- Put native `GetObject` or `PutObject` inputs in the JSON `parameters` object.
The installed SDK model validates names and types; it is not an unchecked
JSON bag or a guarantee that Cloudflare supports every AWS feature.
- Do not send nested `Bucket`, `Key`, or `Body`. Object bytes go directly from
the application to R2. `ChecksumAlgorithm` is rejected because Gateman does
not receive the body; explicit caller-supplied checksums may be signed when
supported by the SDK.
- For PUT, top-level `content_type` is legacy compatibility for
`parameters.ContentType`; matching values are accepted, conflicts are not.
Do not use `content_type` for GET.
- Use the returned `url`, `method`, and every header in `headers` unchanged for
the direct R2 request. Do not filter or reconstruct headers from
`X-Amz-SignedHeaders`, and interpret the actual Cloudflare response.
- Cloudflare credentials and permissions authorize the bucket, key, and
provider action. Do not add bucket registration, prefix checks, or object
authorization to Gateman. Distinguish SDK signing from Cloudflare support.
- One admitted call consumes one `r2.presign` operation. The direct R2
transfer consumes no additional Gateman operation. `X-GM-Idempotency-Key`
does not deduplicate R2 presigning calls.
- Full capability reference:
https://docs.getgateman.com/capabilities/r2.use/
<!-- gateman:end capability=r2.use -->
