Skip to navigation

A presigned URL is a regular HTTPS URL with an embedded signature that grants temporary access to a single object — no Access Key required by the recipient.

Use presigned URLs to:

  • Let a user download a private object from a browser, without giving them credentials.
  • Let a client upload a file directly to your bucket, bypassing your backend.
  • Hand a third-party system temporary access to a single file.

The URL stops working when its expiry passes.

Before you begin

The S3 endpoint for the bucket’s workspace is https://{workspaceId}.blob.ibeestorage.com.

Generate a download URL with the AWS CLI

aws --endpoint-url https://{workspaceId}.blob.ibeestorage.com \
--profile ibee \
s3 presign s3://my-bucket/reports/2026-05.pdf \
--expires-in 3600

The command prints a URL with embedded signature parameters:

https://{workspaceId}.blob.ibeestorage.com/my-bucket/reports/2026-05.pdf?
X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=...
&X-Amz-Date=...
&X-Amz-Expires=3600
&X-Amz-SignedHeaders=host
&X-Amz-Signature=...

Anyone with the URL can GET the object until the expiry passes.

Generate from an SDK

from datetime import timedelta
import boto3
s3 = boto3.client(
"s3",
endpoint_url="https://{workspaceId}.blob.ibeestorage.com",
aws_access_key_id="AKIA...",
aws_secret_access_key="<your-secret-access-key>",
)
url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": "my-bucket", "Key": "reports/2026-05.pdf"},
ExpiresIn=int(timedelta(hours=1).total_seconds()),
)
print(url)

Presigned uploads (PUT)

Generate a URL the client can PUT to directly:

url = s3.generate_presigned_url(
"put_object",
Params={
"Bucket": "my-bucket",
"Key": "uploads/avatar.png",
"ContentType": "image/png",
},
ExpiresIn=600,
)

The client uploads with a single HTTP request:

curl -X PUT "$URL" \
-H "Content-Type: image/png" \
--data-binary @avatar.png

Presigned POST (browser uploads with form data)

POST policies are the right fit for browser-based form uploads — they let you constrain the upload (key prefix, max size, content type) before the file is sent.

post = s3.generate_presigned_post(
Bucket="my-bucket",
Key="uploads/${filename}",
Conditions=[
["content-length-range", 0, 10 * 1024 * 1024], # 10 MB max
["starts-with", "$Content-Type", "image/"],
],
ExpiresIn=600,
)
print(post) # url + fields

Best practices

  • Use the shortest expiry that works. Don’t issue a long-lived URL when 5 minutes is enough.
  • Bind to content type and size for upload URLs to prevent abuse.
  • Don’t log presigned URLs — anyone who reads the log gets the same access.
  • Treat them as one-time secrets — regenerate per request rather than caching.

Limitations

  • The URL stops working if the API token used to sign it is revoked.
  • Each presigned URL is valid for one HTTP method on one key. Different operations need different URLs.