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

# Egocentric video annotation

> Submit first-person videos, follow each job, and download completed annotations.

The Egocentric API annotates first-person videos asynchronously. Submit a batch of video URLs, save the returned batch and job IDs, then poll for completion and download each successful result. Each input becomes one job; all jobs in a batch use the same annotation options.

## Authenticate

Use a [Perceptron API key](https://docs.perceptron.inc/perceptron-mk1.5/guides/python/auth.md) in the `Authorization: Bearer` header on every request, including result downloads. Batches and jobs belong to your organization and can be accessed with another valid key from that organization.

Set `PERCEPTRON_API_KEY` in your environment. The examples use the public base URL `https://api.perceptron.inc` and require `curl` and `jq`.

## Submit a batch

Save the following as `egocentric-request.json`. Replace the example URL with an HTTP(S) URL that serves your video directly. Keep the video accessible while the job is waiting to be processed. See [input requirements and limits](https://docs.perceptron.inc/services/egocentric-limits-and-errors.md#input-requirements).

```json theme={null}
{
  "inputs": [
    {
      "type": "video_url",
      "video_url": {
        "url": "https://media.example.com/videos/preparing-lunch.mp4"
      },
      "filename": "preparing-lunch.mp4"
    }
  ],
  "hand_tracking": {"output": "2d"},
  "hand_activity": true
}
```

Choose a unique idempotency key for this submission and save it with the request. Reuse both if you need to repeat the request after a lost response.

```bash theme={null}
export EGOCENTRIC_IDEMPOTENCY_KEY="lunch-annotation-2026-11-03-001"

curl --fail-with-body --silent --show-error \
  https://api.perceptron.inc/v1/egocentric/batches \
  -H "Authorization: Bearer $PERCEPTRON_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $EGOCENTRIC_IDEMPOTENCY_KEY" \
  --data-binary @egocentric-request.json \
  --output egocentric-batch.json
```

A successful submission returns HTTP `202`, a `Location` header pointing to the batch, and a JSON response containing the batch `id` and `jobs` array. The job IDs appear in the same order as your inputs. Save this mapping so you can associate each result with its source video. Acceptance does not mean the videos have passed media validation or finished processing.

After a successful response, extract the batch ID and the first job ID:

```bash theme={null}
BATCH_ID=$(jq -er '.id' egocentric-batch.json)
JOB_ID=$(jq -er '.jobs[0].id' egocentric-batch.json)
```

See the [submit reference](https://docs.perceptron.inc/api-reference/egocentric/submit-an-egocentric-batch.md) for the request and response schemas.

### Choose annotation options

`hand_tracking` accepts `false` to disable tracking, `true` to use the default `2d` output, or an object such as `{"output": "2d"}` or `{"output": "3d"}`. Omitting it enables `2d` tracking. `hand_activity` is a boolean and defaults to `true`.

The `options` field in the returned batch contains the choices fixed at acceptance, including defaults. For enabled tracking, it returns the explicit output setting. Disabled annotation outputs are absent from the result. Every successful job also includes a timeline of video segments with start and end times. The timeline cannot be disabled, even when both hand options are `false`.

### Repeat a submission safely

Send exactly one `Idempotency-Key` header containing 1–255 visible ASCII characters, with no spaces. Keys are scoped to your organization; changing API keys within that organization does not create a separate key namespace.

Repeating an accepted key with the same request returns `202`, the same batch and job IDs, and the same `Location`. The response shows the batch's **current** progress and timing. It does not create new jobs or restart failed ones.

Reusing that key with a different request returns `409` with `error.code: "idempotency_conflict"`. Keep the original input order, URLs, filenames, and option fields. Adding an explicit default to a request that originally omitted it also changes the request. JSON object property order does not matter.

Every POST attempt counts toward the submit request limit, including replays. For a genuinely new batch, use a new key. See [retry guidance](https://docs.perceptron.inc/services/egocentric-limits-and-errors.md#retry-requests-and-failed-jobs).

## Follow progress

Retrieve the batch to see its `counts` and `done` fields:

```bash theme={null}
curl --fail-with-body --silent --show-error \
  "https://api.perceptron.inc/v1/egocentric/batches/$BATCH_ID" \
  -H "Authorization: Bearer $PERCEPTRON_API_KEY"
```

`done` becomes `true` when every job is either `completed` or `failed`. A finished batch can contain both successful and failed jobs. Ignore any future keys you do not recognize in `counts`, and use `done` to tell when the batch has finished. Use the [batch's job list](https://docs.perceptron.inc/api-reference/egocentric/list-jobs-in-an-egocentric-batch.md) to inspect individual outcomes.

Retrieve a single job when you need its status, timing, or failure details:

```bash theme={null}
curl --fail-with-body --silent --show-error \
  "https://api.perceptron.inc/v1/egocentric/jobs/$JOB_ID" \
  -H "Authorization: Bearer $PERCEPTRON_API_KEY"
```

| Status | Meaning |
| - | - |
| `queued` | Preparing the input or waiting for capacity. |
| `submitted` | Submitted for processing; the job is not yet final. |
| `processing` | Execution has started; the job is not yet final. |
| `completed` | Finished successfully. Download the result before it expires. |
| `failed` | Finished without a result. Inspect the job's `error`. |

`queued` and `submitted` do not establish whether execution has started. Use the timing fields when their values are known.

Only `completed` and `failed` are final. Treat any future, unknown status as unfinished. Allow time between polls and respect the [shared read limit](https://docs.perceptron.inc/services/egocentric-limits-and-errors.md#request-and-submission-limits); avoid a tight polling loop.

### Read timing fields

Use timestamps rather than status changes to measure execution. `created_at` is acceptance time; `started_at` and `execution_finished_at` are actual execution times when known. A `null` execution timestamp can mean the job never ran or the time is unknown.

A job retains its last unfinished status until its result and billing are finalized. Its `finished_at` records that final completion, which can be later than `execution_finished_at`. There is no guaranteed start time or exact completion time.

By default, jobs have a 72-hour execution deadline from `created_at`, including time spent waiting. This does not guarantee completion within 72 hours: result and billing finalization can continue afterward.

For a batch, `started_at` is the earliest known job start. `execution_finished_at` is the latest execution end only after every job has a known end or is confirmed never to have run; it is `null` if any end is unknown or no job ran. Batch `finished_at` is the last job's final completion time and stays `null` until `done` is `true`.

## Download a result

Once a job is `completed`, download its JSON annotations:

```bash theme={null}
curl --fail-with-body --silent --show-error \
  "https://api.perceptron.inc/v1/egocentric/jobs/$JOB_ID/result" \
  -H "Authorization: Bearer $PERCEPTRON_API_KEY" \
  --output egocentric-result.json
```

The [result endpoint](https://docs.perceptron.inc/api-reference/egocentric/download-an-egocentric-result.md) returns the JSON body directly. Only use the saved file after the command succeeds and the JSON parses; an interrupted download can leave an incomplete file.

The job's `expires_at` is 30 days after `finished_at`, not 30 days after submission or execution end. Download results before that timestamp. An expired completed result returns `410` with `result_expired`; an unfinished or failed job returns `409` with `result_unavailable`.

### Understand billing

Acceptance creates pending usage, not a completed charge. Successful jobs are charged using their validated video duration, accepted options, and pricing in effect at acceptance. Billing includes the timeline even when both hand options are `false`. For videos shorter than one minute, each enabled output, including the timeline, is billed as one minute.

Failed jobs are not charged and any reserved credits are released. Replaying an accepted submission does not create another charge.

## List batches and jobs

[List batches](https://docs.perceptron.inc/api-reference/egocentric/list-egocentric-batches.md) or [list jobs](https://docs.perceptron.inc/api-reference/egocentric/list-egocentric-jobs.md) across your organization. Both use creation-time order, newest first by default. Set `order=asc` for oldest first, or `created_after` to include items created at or after an RFC 3339 timestamp.

```bash theme={null}
curl --fail-with-body --silent --show-error --get \
  https://api.perceptron.inc/v1/egocentric/jobs \
  -H "Authorization: Bearer $PERCEPTRON_API_KEY" \
  --data-urlencode "limit=100" \
  --data-urlencode "order=asc" \
  --data-urlencode "created_after=2026-11-01T00:00:00Z" \
  --output egocentric-jobs-page.json
```

When `has_more` is `true`, pass the response's `last_id` as `after` in the next request, keeping the other query parameters unchanged. Omit `after` on the first request. An empty page has empty `first_id` and `last_id` strings.

All list endpoints default to 100 items per page. Batch pages allow up to 100 items; job pages allow up to 1,000. The batch-specific job list always follows the original input order and accepts only `limit` and `after`. Its cursor must identify a job in that batch. Invalid or unavailable cursors return `400`.

> [card] [Egocentric endpoint reference](https://docs.perceptron.inc/api-reference/egocentric/submit-an-egocentric-batch.md)
>
> Request and response schemas for batches, jobs and results.


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