Python
Boto3
AWS
S3
Object URL

Python 3 Boto 3, AWS S3 Get object URL

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

When developers ask for an S3 object URL, they usually mean one of two different things: a normal URL for an object that is already publicly readable, or a pre-signed URL that grants temporary access to a private object. In Boto3, those are different workflows, and choosing the wrong one is the main reason links fail.

Decide Which URL You Actually Need

Before writing code, answer this first:

  • Is the object public to anonymous users
  • Or is the object private and meant to be shared temporarily

If the bucket is private, a plain HTTPS URL is not enough. S3 will reject the request unless the caller is separately authenticated. In that case, the correct solution is a pre-signed URL.

Build a Plain Public Object URL

For a public object, the URL format is predictable. One detail worth handling is URL-encoding the key correctly:

python
1from urllib.parse import quote
2
3
4def public_s3_url(bucket: str, key: str, region: str) -> str:
5    encoded_key = quote(key, safe="/")
6    return f"https://{bucket}.s3.{region}.amazonaws.com/{encoded_key}"
7
8
9print(public_s3_url("my-public-bucket", "images/company logo.png", "us-east-1"))

Encoding matters for spaces and other characters that are valid in an S3 key but not valid as raw URL path characters.

This approach only works if the object is readable through bucket policy, object ACL settings, or another public-access configuration. If the object is private, generating the URL string alone does not grant access.

Generate a Pre-Signed URL for Private Objects

Boto3 provides generate_presigned_url for time-limited access:

python
1import boto3
2
3s3 = boto3.client("s3", region_name="us-east-1")
4
5url = s3.generate_presigned_url(
6    ClientMethod="get_object",
7    Params={
8        "Bucket": "my-private-bucket",
9        "Key": "reports/weekly.csv",
10    },
11    ExpiresIn=900,
12)
13
14print(url)

This URL is signed with your AWS credentials and remains valid for the expiration window. ExpiresIn=900 means fifteen minutes.

For most production applications, this is the safer default because buckets should usually stay private.

Wrap the Logic in a Helper

Centralizing the behavior makes it easier to test and reuse:

python
1import boto3
2from botocore.exceptions import ClientError
3
4
5def get_download_url(bucket: str, key: str, region: str = "us-east-1", expires_in: int = 900) -> str:
6    s3 = boto3.client("s3", region_name=region)
7    try:
8        return s3.generate_presigned_url(
9            "get_object",
10            Params={"Bucket": bucket, "Key": key},
11            ExpiresIn=expires_in,
12        )
13    except ClientError as exc:
14        raise RuntimeError(f"Could not generate URL for s3://{bucket}/{key}") from exc
15
16
17print(get_download_url("my-private-bucket", "docs/manual.pdf"))

This isolates region selection, error handling, and expiration rules in one place instead of scattering them across controllers or API routes.

Control Download Behavior

You can also pre-sign a URL that tells the browser how to handle the download:

python
1import boto3
2
3s3 = boto3.client("s3", region_name="us-east-1")
4
5url = s3.generate_presigned_url(
6    "get_object",
7    Params={
8        "Bucket": "my-private-bucket",
9        "Key": "docs/manual.pdf",
10        "ResponseContentDisposition": "attachment; filename=manual.pdf",
11    },
12    ExpiresIn=300,
13)
14
15print(url)

This is useful when you want the browser to download a file instead of opening it inline, or when you want to suggest a clean filename to the client.

Validate Existence When Your API Needs a Clear Error

Pre-signing a URL does not prove the object exists at request time. If your application should fail early, do a metadata check first:

python
1import boto3
2from botocore.exceptions import ClientError
3
4
5def object_exists(bucket: str, key: str) -> bool:
6    s3 = boto3.client("s3")
7    try:
8        s3.head_object(Bucket=bucket, Key=key)
9        return True
10    except ClientError:
11        return False

This is helpful in API endpoints where returning a clear “not found” response is better than giving the client a signed link that later fails.

Credentials and Region Still Matter

Boto3 resolves credentials from environment variables, local AWS profiles, or IAM roles. The signing operation uses those credentials, so missing or incorrect credentials cause errors before the URL is generated.

Region configuration matters too. S3 may redirect requests when a bucket lives in a different region than the client expects, and misconfigured signing can lead to confusing failures. For deployed systems, IAM roles are preferred over static keys stored in source code or config files.

Common Pitfalls

  • Returning a plain S3 URL for a private object and expecting it to work for anonymous users.
  • Forgetting to URL-encode keys when constructing public URLs manually.
  • Generating very long-lived pre-signed URLs when short-lived access would be safer.
  • Ignoring region mismatches, which can cause redirect or signature problems.
  • Logging full pre-signed URLs, which can expose temporary access tokens in shared logs.

Summary

  • Decide first whether you need a public object URL or a pre-signed private-access URL.
  • For public objects, build the HTTPS URL carefully and encode the key.
  • For private objects, use Boto3 generate_presigned_url.
  • Keep expiration windows short and prefer IAM roles for credentials.
  • Optionally check object existence first when your API needs explicit not-found handling.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.