# Application server API

Build `cipherfs serve` with `sync`. These routes use the existing Bearer/private owner session under `/api/v1` (or a configured vault alias). S3 application keys do not authorize the JSON API.

| Request | Behavior |
| --- | --- |
| `PUT /server/file?path=attachments/a&level=store&timeout_ms=30000` | Stream one complete replacement, commit locally, then wait for at least one store's content confirmation |
| `PUT /server/file?file_id=<id>&level=full_devices&full_devices_count=2` | Replace that existing identity, then wait for two authorized Full devices holding its complete content |
| `GET /server/file?file_id=<id>` | Read the selected identity, with the existing single Range support |
| `GET /server/durability?version=<id>` | Inspect accepted content-holder observations for an exact version |
| `POST /server/durability` | Resume waiting for an existing version without writing another version |
| `GET` / `PUT /server/cache` | Read or replace this device's file pins and heat-prefetch settings |
| `PUT` / `DELETE /server/pins?file_id=<id>` | Add or remove one exact file pin |

A write requires exactly one `path` or `file_id`. `level` defaults to `local`; other choices are `store` and `full_devices` with `full_devices_count` in 1–128. The wait timeout is 1–3,600,000 milliseconds, default 30,000, and begins after local commitment. Invalid options and replication requests for local-only files are refused before consuming the body. Upload buffering, explicit EOF, declared/detected content type and cancellation reuse the ordinary file adapter.

A successful write includes a `receipt` containing the exact `file`, `version` and `local_only` values from its commit transaction. A store/full-device response succeeds only after that version's complete-content position is confirmed. Index-only peers, Partial devices and revoked devices do not count as Full replicas. The current owner counts when it is Full and holds the content. These are authenticated position observations, not a promise that a holder remains reachable forever.

A confirmation timeout returns HTTP 504 and the committed receipt; it does not undo the local write. Other confirmation failures also retain a known receipt. On revocation, an already admitted server write drains its worker and may return an error receipt, never a successful response or plaintext content. A disconnected client may still miss the response; retrying an upload is a new write.

Resume a barrier with:

```json
{
  "version": "<exact-version-id>",
  "requirement": {"level": "full_devices", "count": 2},
  "timeout_ms": 30000
}
```

FileId reads and writes follow moves and do not select a different file that reuses the old path. A concurrent move during preparation can return a conflict requiring retry. Historical reads keep using the existing version APIs.

Cache settings are device-local and sealed in metadata:

```json
{
  "pinned_files": [],
  "prefetch_max_files": 32,
  "prefetch_max_bytes": 67108864,
  "heat_ttl_ms": 3600000
}
```

Pins are sorted unique existing FileIds, up to 4,096, and retain each file's current content across moves and writes. They do not pin descendants or unrelated path replacements. Removing a file can leave a stale pin, which can be removed later. Directory pins remain available separately.

Heat tracks up to 1,024 accessed file identities in memory, decays by age and is cleared on sealing/restart. A Partial device can prefetch newer heads of recently accessed files. Each round selects at most `prefetch_max_files` (maximum 1,024), and missing ciphertext must fit `prefetch_max_bytes` (maximum 8 GiB) and the read-cache budget. A file exceeding the remaining budget is skipped. Set both limits to zero to disable heat prefetch, the default. Heat does not permanently pin data; ordinary eviction rules remain in force. `heat_ttl_ms` is positive and at most seven days.

For short retention, `PUT /retention` accepts the ordinary day fields and an optional `application` object:

```json
{
  "version_max_age_days": 365,
  "trash_max_age_days": 365,
  "min_versions_per_file": 1,
  "grace_days": 30,
  "application": {
    "version_max_age_ms": 60000,
    "trash_max_age_ms": 60000,
    "grace_ms": 10000,
    "local_drop_margin_ms": 10000,
    "store_gc_delay_ms": 10000
  }
}
```

Overrides are vault-wide, authenticated and synchronized. Zero means no delay; each override is bounded to 100 years. Without `application`, the day-based behavior is unchanged. A proposal keeps the timings from its causal policy even after later policy edits. Current/conflict heads and unpublished modifications remain protected. Eligibility is not a real-time deletion guarantee: maintenance scheduling, live pack contents and independent orphan/repack safeguards still apply. The desktop day editor preserves an existing application override. See [format details](FORMAT.md).

Rust applications can use Core's `WriteReceipt`, `open_file_id_handle`, `write_file_id_from_reader`, `pin_file`, `server_cache_policy` and `version_durability`. `SyncedVault` adds `put_from_reader_version`, FileId streaming reads/writes, cache controls and the cancellable `wait_for_durability` barrier. Timeout errors identify the version and last completed observation; a barrier never rolls back an earlier write.
