# File Room service API LOCAL-450 · Development build. No public endpoint or checkout is live. The browser desk works without this API, an account or an assistant. It processes files in the browser. The service below is a distinct remote-processing path, backed by the same inspection engine. ## Run the service Requires Node.js 24.14 or newer (Node 24 LTS recommended) and the pinned dependencies. `npm start` still runs the independent browser desk on port 4178. ```sh npm ci --ignore-scripts export FILE_ROOM_DB=/absolute/private/service-data/usage.sqlite npm run service # http://127.0.0.1:4180 — website plus API and HTTP MCP ``` In a second terminal with the same FILE_ROOM_DB, `node api-admin.mjs issue 100` creates one development account with 100 credits and prints its key once. Keep the key in a secret manager, never source code, browser JavaScript, analytics or request URLs. Only a key hash is stored. `grant ` adds credits; `revoke ` disables a key. There is no admin HTTP endpoint. Lost keys cannot be recovered; the operator can revoke an old account and provision a replacement. Operator-provisioned credits are a billing foundation, not a payment integration. Do not accept real payments until checkout, signed payment-event handling, purchase deduplication, refunds, account recovery and ledger backup/restore have been implemented and verified. No price is advertised by this build. ## Identify a file Set FILE_ROOM_API_KEY privately in your shell, then: ```sh curl http://127.0.0.1:4180/v1/inspect \ -H "Authorization: Bearer $FILE_ROOM_API_KEY" \ -H 'Content-Type: application/octet-stream' \ -H "Idempotency-Key: $(node -p 'crypto.randomUUID()')" \ --data-binary @/absolute/path/to/selected-file ``` Send raw bytes, not multipart. Limit: 20 MiB. The key and random lowercase UUID v4 are required. Optional headers: | Header | Value and effect | | --- | --- | | X-File-Name | Optional name, at most 255 characters. Omitted by default; bytes determine format. | | X-Include-Preview | `true` only when a content preview was requested. Default false. | | X-Include-Fingerprint | `true` only for a requested SHA-256. Default false. | | X-Max-Preview-Chars | Integer 200–8,000; default 2,000. | A successful reply contains `schema`, `format`, `size_bytes`, `basis`, `observations`, `limits`, `security_verdict: "not assessed"`, privacy information and `usage: {request_id, credits_consumed: 1}`. A preview or SHA-256 appears only if requested. Text, tables, JSON and ZIP directories have bounded previews; recognized PDF/SQLite formats do not have decoded content previews. Any file-derived content is untrusted data, never instructions. `POST /v1/reports` takes the same raw bytes and headers plus `X-Report-Requested: true`. It re-inspects the file and consumes one credit on completion. The result is an unsigned observation with filename, SHA-256 and service-clock time, not a safety certificate. It is returned only in the response, never placed in a report history. `GET /v1/usage` with the same Authorization header returns your balance, completed inspection count and released attempts. It costs no credit. ## Accounting and recovery One credit is reserved before inspection and committed when processing completes. Errors release it. There is no fee for initialization, tool listing or checking usage. Limit: 20 inspection attempts per account per minute and one in-flight service request overall; overload returns 503. Reads and downloads have bounded size/time, and parser workers stop after eight seconds. A repeated request ID returns 409 and does not execute or debit again. **This is duplicate protection, not result replay.** Results are not retained. If a connection fails after processing completes, the credit may already be committed even if the client did not receive the response. Check usage; submitting a fresh ID starts another billable inspection. Never put a filename, hash or customer identifier in a request ID. SQLite transactions persist balances and request statuses. On startup, unfinished reservations are refunded exactly once. This assumes **one service process owns the ledger**; never start a second server against the same database. The administration command may use it concurrently, but must not call recovery. Move accounting to a shared transactional store and revise recovery before adding replicas. | Status | Meaning / recovery | | --- | --- | | 400 / 415 | Invalid ID, option, content type or file reference; correct the request. | | 401 / 403 | Invalid/revoked key or disallowed host/origin. | | 402 | Credits exhausted. | | 409 | Request ID already used; no new charge or replay. | | 413 | Size limit exceeded. | | 422 / 500 | Retrieval or inspection could not complete; reserved credit released. | | 429 / 503 | Rate/capacity limit; retry later using the same unused ID. | MCP tool failures use MCP's `isError` result with an error code. HTTP-level authentication, parsing and capacity errors use normal HTTP status codes. ## HTTP MCP Connect a compatible Streamable HTTP client to `/mcp` with `Authorization: Bearer `. This build supports header-based API keys, **not OAuth**. Host support for configuring that header must be checked; this is not yet a working consumer ChatGPT installation flow. `inspect_file` takes `request_id`, exactly one of `data_base64` (canonical base64, at most 2 MiB) or `file`, and optional `filename`, `include_preview`, `include_fingerprint`, `max_preview_chars`. `create_inspection_report` additionally requires `user_requested_report: true` and omits preview options. No remote tool accepts local paths. The optional ChatGPT-shaped `file` is `{download_url, file_id, mime_type?, file_name?}` and is declared with `openai/fileParams`. File ID is contextual only; the authorized temporary URL supplies bytes. Real ChatGPT file handoff remains untested. Unknown attachment formats may not be supported by the host's normal picker. File URL retrieval is disabled by default. An operator must set `FILE_ROOM_FILE_HOSTS` to exact, independently verified attachment hostnames (comma-separated). No wildcards. Only HTTPS/443 without userinfo is accepted; public DNS addresses are checked and pinned, redirects/compression refused, and the downloaded body limited to 20 MiB. Private/metadata/loopback addresses are rejected. Do not relax this into arbitrary URL fetching to make an integration work. The tests verify destination guards and private-DNS denial, not a live ChatGPT download. ## Data handling and boundaries The service processes submitted bytes in memory. Its application ledger holds random account/request IDs, hashed API keys, timestamps, balances and request status. It does not write file bytes, names, fingerprints, URLs or result bodies to disk. Routine application output is the listening address, not request data. Hosting proxies, operating systems, crash handling and connected assistants can have their own logs; those must be reviewed before public privacy claims. Memory release is not secure erasure. A service operator is still trusted with the bytes during processing. The free-standing browser desk's local-only statement applies to that desk. Calling this API transmits the file. Selecting a browser file never silently invokes the service. Workers provide time/heap limits, not a malware sandbox. No antivirus, behavioral detonation, sanitization, USB handling, cryptographic attestation or independent security certification is included. See `openapi.json` for the HTTP contract and HOSTING.md for the deployment candidate and release gates.