> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.ibee.co.in/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.ibee.co.in/docs/_mcp/server.

# Presigned URLs

> Generate time-limited URLs for uploading or downloading IBEE Object Storage objects without sharing your credentials.

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

* A bucket ([Create a bucket](/docs/infrastructure/object-storage/buckets))
* An [API credential](/docs/infrastructure/object-storage/api-tokens) with at least **Object Read & Write** scope on the target bucket — used to sign URLs

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

## Generate a download URL with the AWS CLI

```bash
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:

```text
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

**`Python (boto3)`**

```python title="Python (boto3)"
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)
```

**`Node.js (AWS SDK v3)`**

```javascript title="Node.js (AWS SDK v3)"
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3 = new S3Client({
  endpoint: "https://{workspaceId}.blob.ibeestorage.com",
  credentials: { accessKeyId, secretAccessKey },
});

const url = await getSignedUrl(
  s3,
  new GetObjectCommand({ Bucket: "my-bucket", Key: "reports/2026-05.pdf" }),
  { expiresIn: 3600 }
);
console.log(url);
```

## Presigned uploads (`PUT`)

Generate a URL the client can `PUT` to directly:

```python
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:

```bash
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.

```python
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.

## Related

* [Objects](/docs/infrastructure/object-storage/objects)
* [API Credentials](/docs/infrastructure/object-storage/api-tokens)
* [Bucket policies](/docs/infrastructure/object-storage/buckets/bucket-policies)
* [CORS](/docs/infrastructure/object-storage/buckets/cors)