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

# Run Screenshots

> Fetch signed URLs for screenshots referenced in run message history

## Overview

Completed runs can include screenshots in `run_message_history`. These screenshots are stored as stable `supabase://run-images/...` references, not public HTTPS URLs:

```json theme={null}
{
  "type": "image",
  "image_url": "supabase://run-images/message-history/org_123/1762547216743_5002.png"
}
```

Use the run screenshot signed URL endpoint when you need to programmatically view, download, archive, or audit those images outside the Cyberdesk dashboard.

## Why Signed URLs?

Run screenshots are private organization data. The `supabase://` reference is stable and safe to store, but it is not directly fetchable. A signed URL is a temporary HTTPS URL that grants read access to one screenshot.

<Info>
  Signed URLs expire after the requested `expires_in` value. The maximum is 3600 seconds.
</Info>

## Authorization Model

The endpoint is scoped to both the run and the image reference:

* Your API key must belong to the run's organization.
* The `image_url` must be an exact `supabase://run-images/...` URL present in that run's `run_message_history`.
* Cyberdesk verifies the storage path belongs to the same organization before returning a signed URL.

This means you can safely pass the screenshot references you read from a run response without exposing other organizations' images.

## Endpoint

```http theme={null}
GET /v1/runs/{run_id}/image/signed-url?image_url={image_url}&expires_in=3600
Authorization: Bearer cd_...
```

Example response:

```json theme={null}
{
  "supabase_url": "supabase://run-images/message-history/org_123/1762547216743_5002.png",
  "signed_url": "https://...",
  "expires_in": 3600
}
```

## Curl Example

```bash theme={null}
curl -G "https://api.cyberdesk.io/v1/runs/$RUN_ID/image/signed-url" \
  -H "Authorization: Bearer $CYBERDESK_API_KEY" \
  --data-urlencode "image_url=supabase://run-images/message-history/org_123/1762547216743_5002.png" \
  --data-urlencode "expires_in=3600"
```

## TypeScript Example

```typescript theme={null}
const { data: run, error } = await client.runs.get("run-id");
if (error || !run?.run_message_history) {
  throw error ?? new Error("Run has no message history");
}

const imageUrl = run.run_message_history
  .flatMap((message) => Array.isArray(message.content) ? message.content : [])
  .find((block) => block?.type === "image" && typeof block.image_url === "string")
  ?.image_url;

if (!imageUrl) {
  throw new Error("No screenshot found in run message history");
}

const { data: signed } = await client.runs.getImageSignedUrl("run-id", imageUrl, {
  expires_in: 3600,
});

console.log(signed?.signed_url);
```

## Python Example

```python theme={null}
response = await client.runs.get("run-id")
if response.error or not response.data or not response.data.run_message_history:
    raise response.error or RuntimeError("Run has no message history")

image_url = None
for message in response.data.run_message_history:
    content = message.get("content", [])
    if not isinstance(content, list):
        continue
    for block in content:
        if block.get("type") == "image" and isinstance(block.get("image_url"), str):
            image_url = block["image_url"]
            break
    if image_url:
        break

if not image_url:
    raise RuntimeError("No screenshot found in run message history")

signed = await client.runs.get_image_signed_url(
    "run-id",
    image_url=image_url,
    expires_in=3600,
)

print(signed.data.signed_url)
```

## Common Errors

* `400 Invalid Supabase URL`: The `image_url` is not a valid `supabase://...` reference.
* `400 Only run-images URLs are supported`: The URL points to a different storage bucket.
* `403 Image URL is not present in this run's message history`: The URL was not read from the specified run.
* `403 Image URL does not belong to the organization`: The storage path does not match the authenticated organization.
* `404 Run not found`: The run ID does not exist in the authenticated organization.
