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

# GET /getSharedPresentation/{presentationId}/_t/{token}/...

> Public endpoint that serves the entry HTML and every asset of a shared presentation. Gated by an unguessable token in the URL path.

<Note>
  This is the public viewer endpoint. In normal use, you don't call it directly — you share the dashboard URL (`https://app.slideless.ai/share/{presentationId}?token=...`) and the viewer loads this endpoint in an iframe. Read this page only if you're embedding a deck in a custom iframe or writing an integration.
</Note>

## URL structure

Two forms are supported:

### Path-token form (preferred for iframes)

```
GET https://europe-west1-slideless-ai.cloudfunctions.net/getSharedPresentation/{presentationId}/_t/{token}/{assetPath}
```

`assetPath` empty (or equal to `manifest.entryPath`) → serves the entry HTML.
`assetPath` non-empty → serves that asset from the current version's manifest.

The path-token shape matters: when the entry HTML contains `<img src="./hero.jpg">`, the browser resolves it relative to the iframe's URL. With the token in the path, the relative request naturally includes it too — so assets resolve without any cookie or URL-rewriting magic.

### Flat form (curl-friendly)

```
GET https://europe-west1-slideless-ai.cloudfunctions.net/getSharedPresentation/{presentationId}?token={token}
GET https://europe-west1-slideless-ai.cloudfunctions.net/getSharedPresentation/{presentationId}/{assetPath}?token={token}
```

Same behavior; token in query string instead of path. Use this form for command-line testing.

## Auth

**No header required.** Access is gated by the token — either in the URL path (`/_t/{token}/`) or in the `?token=` query string. The token is 384 bits of entropy (48 random bytes, base64url-encoded), validated with constant-time comparison.

## Version resolution

The served version is determined strictly by the token's `versionMode`:

* `{ type: 'latest' }` → the presentation's `currentVersion`
* `{ type: 'pinned', version: N }` → exactly version N

There is **no `?v=` query parameter** to override this. Recipients cannot see any version other than what their token resolves to. Only the owner, via [listPresentationVersions](/api-reference/list-presentation-versions) / [getPresentationVersion](/api-reference/get-presentation-version), can browse history.

## Response

### Success (200)

`Content-Type` is taken from the manifest (`text/html; charset=utf-8` for the entry, or whatever the asset declared — `image/jpeg`, `video/mp4`, `model/gltf-binary`, …).

Headers:

| Header                    | Value                                                                              |
| ------------------------- | ---------------------------------------------------------------------------------- |
| `Content-Type`            | As declared in the manifest                                                        |
| `Content-Length`          | For non-range responses                                                            |
| `ETag`                    | `"<sha256-of-the-blob>"` — enables client caching                                  |
| `Cache-Control`           | `private, max-age=300, must-revalidate`                                            |
| `Accept-Ranges`           | `bytes`                                                                            |
| `X-Content-Type-Options`  | `nosniff`                                                                          |
| `Content-Security-Policy` | Set on entry HTML responses (sandbox + same-origin assets + `https:` CDNs allowed) |

Side effects on entry HTML requests (fire-and-forget, not awaited):

* Increments the matched token's `accessCount`
* Updates the token's `lastAccessedAt`
* Increments the presentation's `totalViews`
* Updates the presentation's `lastViewedAt`

Asset requests do NOT increment view counts (prevents over-counting: a deck with 20 assets would bump views by 21 otherwise).

### Range (206)

`Range: bytes=<start>-<end>` is honored on asset responses:

```
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1023/824100
Content-Length: 1024
Content-Type: image/jpeg
Accept-Ranges: bytes
```

Each Range response is capped at 50 MB per request to prevent memory spikes. Video players will automatically send multiple Range requests to stream long files.

### ETag (304)

`If-None-Match: "<sha256>"` triggers a `304 Not Modified` response when the asset matches. Browsers cache aggressively across loads.

### Errors

| Status | Code                    | When                                                                                                                                                                         |
| ------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `invalid-argument`      | Missing `presentationId` in path                                                                                                                                             |
| `403`  | `revoked`               | The token has been revoked                                                                                                                                                   |
| `404`  | `not-found`             | Generic — also returned for "share doesn't exist", "token wrong", "asset not in manifest", or "no token provided" (intentionally indistinct to prevent share-ID enumeration) |
| `405`  | `method-not-allowed`    | Used POST/PUT/etc. (only GET + HEAD accepted)                                                                                                                                |
| `410`  | `expired`               | Presentation has an expiration set and it's past                                                                                                                             |
| `416`  | `range-not-satisfiable` | `Range` header requested a byte range outside the blob                                                                                                                       |
| `500`  | `internal`              | Backend error                                                                                                                                                                |

Error responses for the entry HTML path are served as minimal HTML (for iframe rendering). Asset paths return plain text.

## Examples

### Fetch entry HTML (curl)

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -i "https://europe-west1-slideless-ai.cloudfunctions.net/getSharedPresentation/0192f1c3-.../_t/AbCdEf.../"
```

### Fetch an asset (curl)

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -i "https://europe-west1-slideless-ai.cloudfunctions.net/getSharedPresentation/0192f1c3-.../_t/AbCdEf.../images/hero.jpg"
```

### Range request for video (curl)

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -H "Range: bytes=0-1023" -i \
  "https://europe-west1-slideless-ai.cloudfunctions.net/getSharedPresentation/0192f1c3-.../_t/AbCdEf.../video/demo.mp4"
```

### Embedding pattern

If you're building a custom viewer and want to load the deck into an iframe yourself:

```html theme={"theme":{"light":"github-light","dark":"github-dark"}}
<iframe
  src="https://europe-west1-slideless-ai.cloudfunctions.net/getSharedPresentation/0192f1c3-.../_t/AbCdEf.../"
  sandbox="allow-scripts allow-same-origin allow-popups allow-forms"
  width="100%"
  height="600"
></iframe>
```

For most use cases, **don't do this** — share the [Slideless viewer URL](https://app.slideless.ai/share/...) instead. It includes fullscreen, download, and the "Made with Slideless" footer.

## Why the 404s look the same

To prevent attackers from probing share IDs, Slideless returns a generic `404 not-found` for any of:

* The share ID doesn't exist
* The share ID exists but the token is wrong
* No token was provided
* An asset path doesn't resolve in the current manifest

There's no way to tell from the response which of these happened. This is intentional.

## Next

* **[Share tokens concept](/concepts/share-tokens)** — why tokens are safe in URLs.
* **[Versioning](/concepts/versioning)** — why there's no `?v=` override.
* **[POST /commitPresentationVersion](/api-reference/commit-presentation-version)** — how versions and manifests get created.
