> For the complete documentation index, see [llms.txt](https://docs.we360.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.we360.ai/reference/framework/screenshot-and-recording-upload.md).

# Screenshot & Recording Upload

The desktop agent never blocks tracking on media transfer. Screenshots and screen recordings are captured to disk, referenced from the activity event by a small **identifier**, and uploaded **out-of-band** on the regular sync ticks. The telemetry event itself carries no image bytes — only the identifier the backend later resolves to the stored object.

This decoupling means a slow or unreachable object store never delays activity data, and a media upload can retry independently without ever losing an event.

***

## The image identifier

Every screenshot is addressed by an opaque **imageId** — e.g. a date-scoped token, optionally prefixed with `enc/` when the tenant has encryption enabled. The agent computes it at capture time and writes it into the activity event's `image_path`.

The backend is the **single source of truth** for turning an imageId into a storage key:

```
<tenant>/screens/<identity>/<imageId>.png
```

The tenant and identity segments always come from the **authenticated token**, never from the client — so a compromised or crafted identifier can never write into another tenant's or user's space. The agent only ever supplies the opaque imageId; it never constructs a full storage path.

Because the same derivation is used when signing the **upload** URL and when signing the **read** URL, an object written out-of-band lands exactly where the viewer later looks for it.

***

## Upload routes

Which route the agent uses depends on whether the tenant has **screenshot encryption** enabled.

{% tabs %}
{% tab title="Encryption disabled" %}
The full image is uploaded **directly to the object store** via a short-lived pre-signed `PUT` URL requested from the backend. A matching pre-signed URL is used for the thumbnail.

If the direct `PUT` is blocked (e.g. a corporate firewall that allows only the We360.ai API host), the agent transparently falls back to the **data relay** — same bytes, same verb, an allow-listed host.
{% endtab %}

{% tab title="Encryption enabled" %}
Screenshot bytes are encrypted with a **per-tenant key that lives only on the backend** and is never distributed to endpoints. The agent therefore cannot encrypt locally, so the full image is sent through the **data relay**, which is the encryption boundary: it encrypts the bytes server-side and stores the ciphertext at the derived key.

Thumbnails are generated by the relay from the received bytes and stored unencrypted (they contain only a low-resolution preview).
{% endtab %}
{% endtabs %}

### The data relay

The data relay is a media-upload endpoint used both as the encrypted-upload path and as the firewall fallback for direct uploads. It has two wire-compatible implementations — one embedded in the core service, one a standalone service — that the agent cannot tell apart. Authentication is performed at the mesh, which forwards the validated tenant and identity; the relay derives the storage path from those authenticated principals plus the agent-supplied imageId.

For encrypted tenants the standalone relay fetches the tenant's encryption configuration from an internal, service-only API and caches it. If that configuration cannot be retrieved, the upload **fails closed** — the relay never stores plaintext when the encryption state is unknown.

***

## Segregated storage

Screenshots and screen recordings are stored in **separate** backends and are never crossed:

* **Screenshots** → image object store (full image + thumbnail).
* **Screen recordings** → video object store.

The relay routes each upload by media kind to the correct backend. Screen recordings now upload through the relay endpoints as well; the earlier signed-URL recording endpoints remain available but are deprecated.

***

## Resilience & local cleanup

Uploads are asynchronous and self-healing:

* On a transient failure the local file is **kept on disk** and retried on the next sync tick; a blocked direct upload falls back to the relay automatically.
* A local file is deleted **only** on a strictly-validated success response — a genuine relay acknowledgement or a confirmed object-store `PUT`, not merely any `2xx`. A misconfigured or wrong endpoint can never trigger premature deletion.
* As a disk-safety backstop, a file that has still not uploaded **30 days** after capture is pruned.

Because the activity event is synced independently of the bytes, a stuck upload only means a temporarily missing preview in the portal — never a lost event or a lost work record.

***

## Data flow

```mermaid
sequenceDiagram
    participant Agent as Desktop Agent
    participant API as Ingestion / Sign API
    participant Relay as Data Relay
    participant Store as Object Store

    Agent->>Agent: Capture to disk, compute imageId
    Agent->>API: Activity event (image_path = imageId, no bytes)

    alt Encryption disabled
        Agent->>API: Request pre-signed PUT URL (imageId)
        API-->>Agent: Signed URL (derived key)
        Agent->>Store: PUT image + thumbnail
        Note over Agent,Store: On firewall block → fall back to Relay
    else Encryption enabled
        Agent->>Relay: PUT bytes (imageId)
        Relay->>Relay: Fetch tenant key (cached), encrypt
        Relay->>Store: Store ciphertext + thumbnail
    end

    Store-->>Agent: Validated OK → delete local file
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.we360.ai/reference/framework/screenshot-and-recording-upload.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
