---
title: "Submit a screenshot observation"
method: POST
path: "/v1/machines/{machine_id}/observations"
tags: ["machines"]
---

# Submit a screenshot observation

`POST /v1/machines/{machine_id}/observations`

DEVICE TOKEN ONLY. Upload one monotonically sequenced, one-frame PNG/JPEG. The encoded payload is limited to 10,485,760 base64 characters (after an optional exact PNG/JPEG data-URI prefix), and decoded dimensions to 320x240 through 3840x2160. The server verifies format, dimensions, and the optional submitted-byte SHA-256, then strips metadata and re-encodes; the response SHA-256 covers those normalized bytes and may differ. Identical sequence replays are safe; reusing a sequence with different bytes is 409. Live transport frames are encrypted, logically expire after 15 minutes, and are excluded from SSE, webhooks, ordinary logs, and ordinary durable run history. Separately, if a live BYOK Task, Workflow, or schedule uses a frame for a visual decision, the fail-closed pre-provider audit retains an exact model-input copy under the account-lifetime screenshot rules.

## Request body

- ExternalMachineObservationRequest
  - `sequence` integer, required
  - `media_type` 'image/png' | 'image/jpeg', required
  - `screenshot` union, required — One static PNG/JPEG screenshot as raw base64 or an exact data:image/png;base64 / data:image/jpeg;base64 URI. Animated and multi-frame images are rejected. This per-image limit does not multiply the ordinary 15 MiB aggregate JSON request-body cap.
    - string
    - string
    - string
  - `sha256` string, nullable — Optional SHA-256 of the submitted decoded image bytes, before metadata stripping and server normalization.
  - `width` integer, nullable
  - `height` integer, nullable — width and height must be supplied together when either is present.

## Response `200`

Verified observation, including its canonical frame_id and decoded dimensions.

- ExternalMachineObservationResponse
  - `machine_id` string, required
  - `frame_id` string, uuid, required
  - `sequence` integer, required
  - `sha256` string, required — SHA-256 of the server-normalized PNG/JPEG bytes. Metadata removal and re-encoding mean this may differ from the request sha256.
  - `width` integer, required
  - `height` integer, required
  - `received_at` string, date-time, required
  - `fencing_token` integer, required
  - `request_id` string, required

## Other responses

- `400` — Invalid request body or parameters.
- `401` — Missing, malformed, revoked, or machine-mismatched external device token. Pass the one-time machine token as `Authorization: Bearer <device_token>`; owner API keys are not accepted on device-plane routes. Revoked tokens may return DEVICE_REVOKED.
- `403` — API key lacks the required scope or tier-feature is unavailable on the caller's plan.
- `404` — Resource not found in this key's namespace.
- `409` — The resource state conflicts with this operation.
- `413` — The request body exceeds the endpoint limit.
- `422` — The JSON shape is valid but one or more values violate the endpoint contract.
- `429` — Rate or concurrency limit exceeded.
- `500` — Unexpected server error. Retry with exponential backoff.
- `502` — An upstream dependency returned an invalid response.
- `503` — A required service is temporarily unavailable.
- `504` — An upstream dependency timed out.

---

[API](https://skmtc.net/coasty/apis/coasty-public-api.md) · [All operations](https://skmtc.net/coasty/apis/coasty-public-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/coasty/coasty-public-api/revisions/f50d93b0d8a1/schema)
