> ## Documentation Index
> Fetch the complete documentation index at: https://upstash.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Reading

> get, info, exists and list, public URLs, and signed read URLs on a private bucket.

This page covers reading objects from your server: `get` for the bytes, `info` for the facts, `exists` for a boolean, `list` for a page of keys, and signed read URLs for a link that reads a private object without going through your server.

```ts TypeScript theme={"system"}
const res = await bucket.get("reports/2026-01.pdf")    // record plus body stream
const info = await bucket.info("reports/2026-01.pdf")  // record only
const ok = await bucket.exists("reports/2026-01.pdf")  // boolean
const page = await bucket.list({ prefix: "reports/" }) // one page of records
```

```python Python theme={"system"}
res = bucket.get("reports/2026-01.pdf")     # record plus the bytes
info = bucket.info("reports/2026-01.pdf")   # record only
ok = bucket.exists("reports/2026-01.pdf")   # bool
page = bucket.list(prefix="reports/")       # one page of records
```

```go Go theme={"system"}
// record plus body stream; close it
res, err := bucket.Get(ctx, "reports/2026-01.pdf")
// record only
info, err := bucket.Info(ctx, "reports/2026-01.pdf")
// bool
ok, err := bucket.Exists(ctx, "reports/2026-01.pdf")
// one page of records
page, err := bucket.List(ctx, &blob.ListOptions{Prefix: "reports/"})
```

The records these return are on [Types](https://upstash.com/docs/blob/reference/types#records). Method signatures and option types: [TypeScript](https://upstash.com/docs/blob/sdks/ts/bucket#get), [Python](https://upstash.com/docs/blob/sdks/py/bucket#get), [Go](https://upstash.com/docs/blob/sdks/go/bucket#get).

***

## get

```ts TypeScript theme={"system"}
const res = await bucket.get("reports/2026-01.pdf")

res.contentType  // 'application/pdf'
res.metadata     // { owner: 'u7' }
res.body         // ReadableStream<Uint8Array>
```

```python Python theme={"system"}
res = bucket.get("reports/2026-01.pdf")

res.content_type  # 'application/pdf'
res.metadata      # {'owner': 'u7'}
res.content       # bytes, read in full; bucket.stream() leaves them on the wire
```

```go Go theme={"system"}
res, err := bucket.Get(ctx, "reports/2026-01.pdf")
if err != nil {
	return err
}
defer res.Body.Close()

res.ContentType // "application/pdf"
res.Metadata    // map[owner:u7]
res.Body        // io.ReadCloser
```

`get` returns the object's record, with its content type and metadata, plus the body. In TypeScript and Go the body is a stream and nothing is buffered for you; Python's `get` reads it in full, and `stream()` does not. Reading the body in each SDK: [TypeScript](https://upstash.com/docs/blob/sdks/ts/bucket#get), [Python](https://upstash.com/docs/blob/sdks/py/bucket#get), [Go](https://upstash.com/docs/blob/sdks/go/bucket#get).

A missing object throws a `BlobError` with code `not_found`, status 404. See [Errors](https://upstash.com/docs/blob/reference/errors).

There is no range option. For byte ranges, use an [S3 client](https://upstash.com/docs/blob/bucket/connecting#using-an-s3-client).

***

## info

```ts TypeScript theme={"system"}
const info = await bucket.info("reports/2026-01.pdf")

info.size         // 184_302
info.etag         // '"9f3c..."'
info.contentType  // 'application/pdf'
info.metadata     // { owner: 'u7' }
```

```python Python theme={"system"}
info = bucket.info("reports/2026-01.pdf")

info.size          # 184302
info.etag          # '"9f3c..."'
info.content_type  # 'application/pdf'
info.metadata      # {'owner': 'u7'}
```

```go Go theme={"system"}
info, err := bucket.Info(ctx, "reports/2026-01.pdf")

info.Size        // 184302
info.ETag        // "\"9f3c...\""
info.ContentType // "application/pdf"
info.Metadata    // map[owner:u7]
```

One HEAD request. The same record as `get` without the bytes, so reading a 2 GB object's facts is cheap. A missing object throws `not_found`.

Metadata comes back from `get` and `info` only, with keys lowercased. See [Metadata](https://upstash.com/docs/blob/bucket/writing#metadata).

***

## exists

```ts TypeScript theme={"system"}
if (await bucket.exists("avatars/u7.png")) {
  // ...
}
```

```python Python theme={"system"}
if bucket.exists("avatars/u7.png"):
    ...
```

```go Go theme={"system"}
ok, err := bucket.Exists(ctx, "avatars/u7.png")
if err != nil {
	return err
}
if ok {
	// ...
}
```

The same HEAD request as `info`, answering false instead of throwing.

If you need the etag, size or metadata anyway, call `info` and catch `not_found` instead of making two round trips.

***

## list

```ts TypeScript theme={"system"}
let cursor: string | undefined

do {
  const page = await bucket.list({ prefix: "avatars/", limit: 1000, cursor })
  for (const blob of page.blobs) console.log(blob.path)
  cursor = page.cursor
} while (cursor)
```

```python Python theme={"system"}
cursor = None

while True:
    page = bucket.list(prefix="avatars/", limit=1000, cursor=cursor)
    for blob in page.blobs:
        print(blob.path)
    cursor = page.cursor
    if not cursor:
        break
```

```go Go theme={"system"}
cursor := ""

for {
	page, err := bucket.List(ctx, &blob.ListOptions{
		Prefix: "avatars/",
		Limit:  1000,
		Cursor: cursor,
	})
	if err != nil {
		return err
	}
	for _, b := range page.Blobs {
		fmt.Println(b.Path)
	}
	if page.Cursor == "" {
		break
	}
	cursor = page.Cursor
}
```

`list` returns one page of records and a cursor. The cursor is set only while more remains; pass it back for the next page. A full walk loops until it is unset.

All three options are optional.

| Option | Default | Description |
| - | - | - |
| Prefix | none, the whole bucket | Only keys starting with this. |
| Limit | storage picks, at most 1000 | Page size, clamped to 1 to 1000. |
| Cursor | none, the first page | The cursor from the previous page. |

Each entry carries path, size, etag, timestamp and URLs. There is no content type or metadata; reading those is one `info` per object.

The prefix is the only filter. There is no query by owner, type or date, so "this user's files" has to be a prefix you chose at upload time. An app that needs to query its files should keep its own table and treat the bucket as storage, not an index.

***

## Public URLs

```ts TypeScript theme={"system"}
await bucket.publicUrl("avatars/u7.png")
// 'https://b0f3a91c24d.blob.upstash.io/avatars/u7.png'
```

```python Python theme={"system"}
bucket.public_url("avatars/u7.png")
# 'https://b0f3a91c24d.blob.upstash.io/avatars/u7.png'
```

```go Go theme={"system"}
bucket.PublicURL(ctx, "avatars/u7.png")
// "https://b0f3a91c24d.blob.upstash.io/avatars/u7.png"
```

Every record on a public bucket already carries its URL; the public URL call gives you one for any path. It returns nothing on a private bucket. An empty path, or one with a `.` or `..` segment, is rejected before any request is sent; see [Paths](https://upstash.com/docs/blob/bucket/writing#paths).

The URL itself is built from the token, but whether the bucket has a public host at all is known only to the backend, so the first call on a fresh client fetches credentials. They are cached, so every call after that is local.

### Versioned URLs

```ts TypeScript theme={"system"}
const avatar = await bucket.info(`avatars/${user.id}.png`)
avatar.versionedUrl // https://.../avatars/u7.png?v=%229f3c...%22
```

```python Python theme={"system"}
avatar = bucket.info(f"avatars/{user.id}.png")
avatar.versioned_url  # https://.../avatars/u7.png?v=%229f3c...%22
```

```go Go theme={"system"}
avatar, err := bucket.Info(ctx, "avatars/"+user.ID+".png")
avatar.VersionedURL // https://.../avatars/u7.png?v=%229f3c...%22
```

```tsx theme={"system"}
<img src={avatar.versionedUrl} />
```

The versioned URL is the URL with `?v=<etag>` appended, so it changes whenever the content does. Use it for a stable path that gets overwritten: if `avatars/u7.png` is replaced every time the user picks a new picture, the plain URL never changes and caches keep serving the old bytes.

Pair it with `cache: 'immutable'` at upload. See [Caching](https://upstash.com/docs/blob/bucket/caching#immutable-plus-a-versioned-url).

***

## Private buckets

```ts TypeScript theme={"system"}
const blob = await bucket.put("reports/2026-01.pdf", pdf) // a bucket set to private in the console
blob.url          // undefined
blob.versionedUrl // undefined
await bucket.publicUrl("reports/2026-01.pdf")  // undefined
```

```python Python theme={"system"}
blob = bucket.put("reports/2026-01.pdf", pdf)  # a bucket set to private in the console
blob.url            # None
blob.versioned_url  # None
bucket.public_url("reports/2026-01.pdf")  # None
```

```go Go theme={"system"}
// a bucket set to private in the console
b, err := bucket.Put(ctx, "reports/2026-01.pdf", pdf, nil)
b.URL          // ""
b.VersionedURL // ""

bucket.PublicURL(ctx, "reports/2026-01.pdf") // ""
```

A private bucket has no public host, so no record carries a URL or versioned URL. Nothing in the code declares this: the SDK learns it from the backend when it fetches credentials, and objects are stored with `Cache-Control: private`. Reads go through signed read URLs.

***

## Signed read URLs

```ts TypeScript theme={"system"}
const { url, expiresAt } = await bucket.signedReadUrl("reports/2026-01.pdf", {
  expiresIn: "2m",
  downloadAs: "Report Q3.pdf",
})
```

```python Python theme={"system"}
link = bucket.signed_read_url("reports/2026-01.pdf", expires_in="2m", download_as="Report Q3.pdf")
link.url, link.expires_at
```

```go Go theme={"system"}
link, err := bucket.SignedReadURL(ctx, "reports/2026-01.pdf",
	&blob.SignedReadURLOptions{
		ExpiresIn:  2 * time.Minute,
		DownloadAs: "Report Q3.pdf",
	})
link.URL       // the link
link.ExpiresAt // when it stops working
```

A time-limited URL anyone can GET, returned with the moment it expires. Use it on a private bucket, or for an object you do not want linked from a public page. Names and types: [TypeScript](https://upstash.com/docs/blob/sdks/ts/bucket#signedreadurl), [Python](https://upstash.com/docs/blob/sdks/py/bucket#signed_read_url), [Go](https://upstash.com/docs/blob/sdks/go/bucket#signedreadurl).

All three options are optional.

| Option | Default | Description |
| - | - | - |
| Expires in | `'5m'` | How long to ask for, at most `'10m'`. `'2m'`, `'10m'`, or a bare number of seconds. A longer ask gets 10 minutes. |
| Download as | none, displayed inline | Save as this filename instead of displaying inline. |
| Content type | the stored type | What storage answers with as `Content-Type`, overriding what was stored. |

### Use the returned expiry

```ts TypeScript theme={"system"}
const cached = await cache.get(key)
if (!cached || cached.expiresAt < new Date()) {
  const link = await bucket.signedReadUrl(path, { expiresIn: "5m" })
  await cache.set(key, link)
}
```

```python Python theme={"system"}
from datetime import datetime, timezone

link = cache.get(key)
if link is None or link.expires_at < datetime.now(timezone.utc):
    link = bucket.signed_read_url(path, expires_in="5m")
    cache.set(key, link)
```

```go Go theme={"system"}
link, ok := cache.Get(key)
if !ok || link.ExpiresAt.Before(time.Now()) {
	opts := &blob.SignedReadURLOptions{ExpiresIn: 5 * time.Minute}
	fresh, err := bucket.SignedReadURL(ctx, path, opts)
	if err != nil {
		return err
	}
	link = fresh
	cache.Set(key, link)
}
```

What you pass as expires-in is what you asked for. The expiry that comes back is what you got, and it can be sooner, because no link lives longer than 10 minutes. Cache the link until the returned expiry, never until a deadline you compute yourself. This applies to signed upload URLs too. The reason is on [How signing works](https://upstash.com/docs/blob/reference/signing#how-long-a-presigned-url-lives).

### Download filename

```ts TypeScript theme={"system"}
await bucket.signedReadUrl(path, { downloadAs: "café ☕.pdf" })
```

```python Python theme={"system"}
bucket.signed_read_url(path, download_as="café ☕.pdf")
```

```go Go theme={"system"}
bucket.SignedReadURL(ctx, path, &blob.SignedReadURLOptions{
	DownloadAs: "café ☕.pdf",
})
```

Sets `Content-Disposition: attachment`, so the browser saves the file under that name rather than rendering it. Unicode names arrive intact. The filename is signed into the URL, so it cannot be edited afterwards.

### Content type override

```ts TypeScript theme={"system"}
await bucket.signedReadUrl("exports/rows.bin", { contentType: "text/csv" })
```

```python Python theme={"system"}
bucket.signed_read_url("exports/rows.bin", content_type="text/csv")
```

```go Go theme={"system"}
bucket.SignedReadURL(ctx, "exports/rows.bin", &blob.SignedReadURLOptions{
	ContentType: "text/csv",
})
```

Overrides what storage answers with, without rewriting the object. Refused with `invalid_input` if it is not a valid media type.

***

## Next steps

> [card] [Writing](https://upstash.com/docs/blob/bucket/writing)
>
> `put`, metadata, conditional writes and multipart from the server.

> [card] [Caching](https://upstash.com/docs/blob/bucket/caching)
>
> What `Cache-Control` an object is stored with, and pairing it with a versioned URL.

> [card] [Deleting](https://upstash.com/docs/blob/bucket/deleting)
>
> One path, a list, a prefix, and sweeping incomplete uploads.

> [card] [Errors](https://upstash.com/docs/blob/reference/errors)
>
> The error codes, what reaches the browser, and platform body limits.


## Related topics

- [Reading Pages](https://upstash.com/docs/box/overall/browser/reading-pages.md)
- [Documentation Crawler](https://upstash.com/docs/search/tools/documentationcrawler.md)
- [Connecting](https://upstash.com/docs/blob/bucket/connecting.md)
- [ACL LOG](https://upstash.com/docs/redis/commands/server/acl-log.md)
- [Playwright Web Scraping with a Cloud Browser](https://upstash.com/docs/box/guides/web-scraping-playwright.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.