# RouteReplay agent instructions

Docs version: skill 1.1.0, scene schema v1. Endpoints: https://routereplay-web.pages.dev/api/v1. Schema: https://routereplay-web.pages.dev/schemas/scene-v1.json.

## Optional skill installation

If you can install skills and the `routereplay-web` skill is not already installed, offer once to install it for future sessions. It contains these workflow instructions and fetches this page for current contracts. Download it from https://routereplay-web.pages.dev/skills/routereplay-web.zip (a folder containing SKILL.md), or read https://routereplay-web.pages.dev/skills/routereplay-web/SKILL.md. Follow your platform's normal installation method and the creator's choice. Do not overwrite a different existing skill without asking, and do not repeat the offer in the same session.

Whether installation is accepted, declined, unavailable, or fails, continue helping the creator with the workflow below. Installing instructions does not give you HTTP or browser capabilities you lack.

## Workflow

RouteReplay turns a flight itinerary into an animated journey on a 3D Earth. You (the agent) research and clarify the itinerary with the creator, then submit a small JSON scene spec over HTTPS. RouteReplay validates it, stores a private draft, and shows it in a live browser preview. You do the authoring; the page only plays, shares, and exports.

You need to be able to make HTTP requests with custom headers and JSON bodies (for example with `curl` or a fetch tool). Reading URLs alone is not enough. If you cannot make authorized POST and PUT requests, tell the creator plainly instead of pretending to create the journey.

### 1. Gather the itinerary

- Collect each flight in order: departure airport, arrival airport, and local departure and arrival times with dates.
- Use 3-letter IATA airport codes (`ORD`, not `KORD` or the city code `CHI`).
- Write every time with the airport's local UTC offset at that moment, including daylight saving time: `2031-07-05T10:00:00-05:00` in Chicago in July, `-06:00` in January.
- Clocks can change during a trip, even overnight on a flight. Check each timestamp's offset separately. If you have no time-zone database, `GET https://routereplay-web.pages.dev/api/v1/airports/{IATA}?local=2031-03-30T06:40` returns the airport, its IANA zone, and the exact offset (or both offsets, or none) for that local time.
- Flights over the date line often arrive on a different calendar day. Check that arrival is after departure in UTC.
- Ask the creator only about material ambiguity: a missing or unclear time or date, a city with several airports, or a gap between flights that suggests a missing leg. Do not invent times. Where the creator has a booking, prefer its scheduled times.
- Leave out passenger names, booking references, ticket numbers, seat numbers, prices, and sources. The scene contains only what is needed to draw the journey.

### 2. Build the spec

Start from the example in the Reference section of https://routereplay-web.pages.dev/agent-docs and the field descriptions in https://routereplay-web.pages.dev/schemas/scene-v1.json. The example sets several optional fields only to show their shape. Use stable lowercase leg ids such as `ord-sfo`. Omit optional presentation fields unless the creator asked for something specific; defaults adapt when legs change. A short, generic `title` (such as "Boston to Madeira") is recommended; it appears beside the player, not in the scene.

Sensible choices:

- `presentation.aspectRatio`: `16:9` (default) for desktop viewing, `9:16` for a phone-first vertical clip, `1:1` for square social posts.
- `presentation.camera`: `globe` (default, whole region in view) or `follow` (behind the aircraft).
- Long connections are animated in a short stop; set `presentation.stopHolds` to give a connection more or less time, or `0` to cut straight to the next departure.
- Pacing: `normalized.legs[].flightSeconds` shows how long each flight is on screen. Flight time is the total duration minus all holds, shared between legs by weight. Default weights grow with distance and average 1; a leg you don't list in `legWeights` keeps its default. Raising one leg's weight alone takes time from the others, and raising `durationSeconds` alone slows every leg. To slow just one leg, add the extra seconds to `durationSeconds` and raise that leg's weight to match. Dry-run with `/api/v1/validate` to see the resulting `flightSeconds` before saving.
- If `durationSeconds` is omitted, the length is the start, end, and stop holds plus 5 seconds per leg (at least 6), capped at 120. It changes when you add legs or holds.

If you are unsure whether a spec is valid, `POST https://routereplay-web.pages.dev/api/v1/validate` checks it without saving anything.

### 3. Create the draft

```
POST https://routereplay-web.pages.dev/api/v1/scenes
Content-Type: application/json
Idempotency-Key: <new random UUID>

<scene spec JSON>
```

The `201` response contains `id`, `revision`, `previewUrl`, and `credentials.edit` / `credentials.owner`. Credentials are shown only once. Capture the whole response before printing or redacting anything, and keep the credentials in this conversation's working state. If you lose them within 24 hours, repeating the identical request with the same `Idempotency-Key` returns the original response, credentials included.

- Give the creator `previewUrl`. Opening it shows the journey (on desktop or phone) and refreshes automatically when you revise it. Its credential, after the `#`, allows editing, so the creator should share it only with people who may edit.
- Keep `credentials.owner` private. It is needed to publish, delete, or rotate the edit credential. Offer it to the creator in your private conversation to keep somewhere safe, such as a password manager, so they can manage the journey after this conversation ends; there are no accounts and it cannot be recovered.
- Never put either credential in a public place. If you must keep credentials between sessions, store them only where the creator approves, never in a skill folder, a repository, or anything shared.
- If the request fails with a network error, retry with the same `Idempotency-Key` and the same body; you will not create a duplicate.
- The `#k=` part of `previewUrl` is the edit credential. The page moves it into a private browser session and removes it from the address bar. For a link that can only watch, publish (step 6).

### 4. Repair validation errors

A `422 validation_failed` response lists `issues`, each with a JSON Pointer `path`, a `code`, a `message`, and often a `hint` with a concrete fix. Nothing is saved. Apply the fixes and resubmit. If an issue reflects a real ambiguity, such as an unknown airport or a time that does not exist, ask the creator rather than guessing. Stop and explain after a few failed attempts.

### 5. Revise

When the creator asks for a change ("slow down the Pacific leg", "make it vertical", "the second flight left at 9:40"):

1. `GET https://routereplay-web.pages.dev/api/v1/scenes/{id}` with `Authorization: Bearer <edit or owner credential>`.
2. Change the returned `spec` (not `normalized`), keeping leg ids stable.
3. `PUT https://routereplay-web.pages.dev/api/v1/scenes/{id}` with body `{"expectedRevision": <revision you read>, "spec": <changed spec>}`.

A `409 revision_conflict` means the draft changed since you read it (perhaps the creator used another agent). The response includes the current spec. Reapply your intended change to it and retry with the current revision; do not blindly overwrite. The open preview picks up the new revision within a few seconds.

To undo, list `GET /api/v1/scenes/{id}/revisions`, fetch an earlier revision, and PUT its `spec` as a new revision.

### 6. Share

Publish only when the creator asks to share:

```
POST https://routereplay-web.pages.dev/api/v1/scenes/{id}/publications
Authorization: Bearer <owner credential>
Content-Type: application/json
Idempotency-Key: <new random UUID>

{"revision": <the revision the creator approved>}
```

Give the creator the returned `watchUrl`. Anyone can watch it without an account or credentials. It is an immutable snapshot: later edits do not change it, and publishing again creates a new link. Published data contains the journey and presentation only.

### 7. Delete

When the creator asks, `DELETE https://routereplay-web.pages.dev/api/v1/scenes/{id}` with the owner credential removes the draft, its revisions, and all its watch links. Explain that this cannot be undone.

### Browser tools (optional)

When the creator has the preview open in a browser that supports WebMCP, the page offers tools: `get_scene`, `set_scene`, `seek_scene`, and (with owner access) `publish_scene`. They use the same validation and revisions as the HTTP API. Use `seek_scene` to inspect a specific moment. The HTTP API works without them.

### Video export

The page has an Export button. On browsers that support it, the creator can save a 720p MP4 locally. Export runs on their device, may take longer than the clip's length, and is not required for sharing.

## Reference

### Example spec (synthetic)

This example sets optional presentation fields to show their shape. Only `schemaVersion` and `legs` are required.

```json
{
  "schemaVersion": 1,
  "title": "Chicago to Auckland (synthetic example)",
  "legs": [
    {
      "id": "ord-sfo",
      "from": "ORD",
      "to": "SFO",
      "departure": "2031-01-05T10:00:00-06:00",
      "arrival": "2031-01-05T12:45:00-08:00"
    },
    {
      "id": "sfo-akl",
      "from": "SFO",
      "to": "AKL",
      "departure": "2031-01-05T21:30:00-08:00",
      "arrival": "2031-01-07T07:15:00+13:00"
    }
  ],
  "presentation": {
    "durationSeconds": 20,
    "aspectRatio": "16:9",
    "camera": "globe",
    "stopHolds": {
      "ord-sfo": 2
    }
  }
}
```

### Endpoints

| Method and path | Purpose | Credential |
| --- | --- | --- |
| `GET /api/v1/airports/{IATA}?local=YYYY-MM-DDTHH:MM` | Airport name, coordinates, IANA zone; with `local`, the valid offset(s) for that local time | None |
| `POST /api/v1/validate` | Check a spec without saving | None |
| `POST /api/v1/scenes` | Validate and create a private draft | None (rate-limited) |
| `GET /api/v1/scenes/{id}` | Read spec, normalized record, revision, capabilities, publications | Edit or owner |
| `PUT /api/v1/scenes/{id}` | Replace the spec: `{"expectedRevision", "spec"}` | Edit or owner |
| `GET /api/v1/scenes/{id}/revisions` | List retained revisions (last 50) | Edit or owner |
| `GET /api/v1/scenes/{id}/revisions/{n}` | Read one revision | Edit or owner |
| `POST /api/v1/scenes/{id}/publications` | Publish a revision as an immutable snapshot: `{"revision"}` | Owner |
| `GET /api/v1/scenes/{id}/publications` | List this draft's watch links | Edit or owner |
| `POST /api/v1/scenes/{id}/credentials/edit` | Replace the edit credential; old one and its browser sessions stop working | Owner |
| `DELETE /api/v1/scenes/{id}` | Delete the draft and all its publications | Owner |
| `GET /api/v1/publications/{id}` | Read a published snapshot | None |

Send credentials as `Authorization: Bearer <credential>`. Responses include `capabilities` so you can see what the current credential allows. Ordinary reads never return credentials. `GET /api/v1/scenes/{id}` supports `If-None-Match` with its `ETag`.

### Spec rules

- `schemaVersion` is `1`. Unknown fields are errors.
- 1–12 legs. Each leg: `id`, `from`, `to`, `departure`, `arrival`.
- Legs are connected (each departs where the previous arrived) and do not overlap in time. Repeated airports are fine.
- Timestamps are ISO 8601 with the airport's local offset at that instant. `Z` is accepted only where the local offset is zero. Times are converted to UTC by the service.
- Flight time per leg: 10 minutes to 30 hours. Whole journey: at most 366 days.
- `presentation.durationSeconds`: 4–120. After start, stop, and end holds are subtracted, each flight needs at least 1 s.
- `presentation.legWeights` values 0.1–10; `stopHolds` values 0–20 s keyed by the arriving leg's id; start and end holds 0–10 s.
- `title` (optional): up to 80 characters, shown beside the player and never drawn in the scene.
- `environment.weather`: only `omitted` (no clouds, no weather claims). `assets.aircraft`: `airliner`. `assets.style`: `standard`.
- Request bodies are limited to 64 KiB.

The service returns `normalized`: resolved airports (name, coordinates, IANA time zone), UTC times, distances, per-leg presentation seconds, defaults, a seed, and pinned renderer, asset, and airport-data versions. Read it to confirm the service understood the itinerary. Never send `normalized` back; edit `spec`.

### Errors

Every error is JSON: `{"error": {"code", "message", "hint"?, ...}}`.

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `invalid_json`, `invalid_body`, `expected_revision_required`, `revision_required`, `invalid_idempotency_key` | Malformed request |
| 401 | `credential_required`, `session_expired` | Missing or expired credential |
| 403 | `owner_required`, `csrf_rejected` | Credential cannot do this |
| 404 | `not_found`, `publication_not_found`, `revision_not_found` | Unknown id, or credential does not match |
| 409 | `revision_conflict` | Draft changed; body has `currentRevision`, `spec`, `normalized` |
| 409 | `request_in_progress` | Same Idempotency-Key still running; retry shortly |
| 410 | `publication_deleted` | Owner deleted the journey |
| 413 | `payload_too_large` | Body over the size limit |
| 415 | `unsupported_media_type` | Use `Content-Type: application/json` |
| 422 | `validation_failed` | `issues[]` with `path`, `code`, `message`, `hint` |
| 422 | `idempotency_key_reused` | Same key, different body |
| 429 | `rate_limited` | Wait `retryAfterSeconds` (also in `Retry-After`) |

Validation issue codes include `unknown_field`, `required`, `type`, `unsupported_version`, `unsupported_preset`, `pattern`, `out_of_range`, `timestamp_format`, `invalid_date`, `offset_mismatch`, `unknown_airport`, `same_airport`, `antipodal_route`, `arrival_not_after_departure`, `flight_duration`, `disconnected_legs`, `overlapping_legs`, `duplicate_leg_id`, `unknown_leg_reference`, `hold_on_final_leg`, `journey_too_long`, and `duration_budget`.

### Limits and retention

- Creation is rate-limited per client and globally; writes are rate-limited per draft. Back off on `429`.
- Idempotency keys are remembered for 24 hours, scoped to the operation (and the draft, for publishing).
- Drafts are deleted after 180 days without changes. Published watch links stay until the owner deletes the draft.
- There are no accounts. Whoever holds the owner credential controls the draft. If it is lost, the draft cannot be published or deleted, and RouteReplay cannot recover it.

### Privacy

Specs must not contain personal data beyond the journey itself. RouteReplay stores the spec and its derived record, never your conversation or the creator's documents. Credentials are stored only as hashes. Video export runs entirely in the viewer's browser and uploads nothing; the bundled Remotion renderer reports only the site's origin and whether a render succeeded to Remotion's license service.
