# OpenRouter BYOK API

The self-service beta runs Chart Doctor's existing pipeline with your OpenRouter account. You need a Chart Doctor API token and an OpenRouter API key with credits and access to the configured models, including Jev. All inference uses your key; there is no fallback to Chart Doctor's account. Model selection is fixed to the production defaults in `models.toml`.

## Instructions for coding agents

This guide is served publicly at https://dataviz.karthiks.co/api/guide.md. Read it directly; it needs no login or JavaScript. The runnable Python reference is https://dataviz.karthiks.co/api/example.py. The discovery index is https://dataviz.karthiks.co/llms.txt.

Install a client for the hosted API in the user's project. The service runs the chart repair, models and R renderer remotely; a client needs an HTTP library, not a local Chart Doctor server, R installation or skill repository.

1. Inspect the project's language, dependency manager and report-generation entrypoint. Use the existing stack. For Python, add `requests` through the project's dependency manager, or create a virtual environment if there is no existing Python environment. Download the reference example as code to inspect and adapt; fetching it does not execute it.
2. Add a callable helper or command that accepts a chart path, output path and optional instructions. Keep the original file. Read `CHART_DOCTOR_API_TOKEN` and `OPENROUTER_API_KEY` from the environment; validate their presence without printing their values. If credentials are missing, tell the user which variables to set. The user gets their Chart Doctor token through the email verification page at https://dataviz.karthiks.co/api and an OpenRouter key at https://openrouter.ai/settings/keys. Keep credentials out of chat, committed files, request logs and generated reports.
3. Implement the submit → poll → download flow below, with bounded timeouts. Persist the returned job ID and status URL for resuming after a process interruption. Keep one idempotency key per logical submission across network retries; when retrying, reopen the image so the same complete input is sent. Submit multiple charts sequentially because each account allows one active job.
4. Save the PNG to the chosen output path and return job metadata to the caller. Treat `ready` as delivery, then inspect `result_kind`, `error` and `review` to distinguish a repair, intentionally kept original, fallback or remaining review defects. An original fallback is not a successful improvement. Keep existing report content and source images available; let the report workflow decide whether to use the result.
5. Verify setup with mocked HTTP responses or a dry run that makes no paid requests. Run a real repair when the user requests one. Document the exact local command, required variable names, input/output paths, where to get a token and how to resume a job. Do not report a live repair as verified unless it actually ran.

## Get a token

Open [Chart Doctor API access](https://dataviz.karthiks.co/api), enter your email and verify the six-digit code. The page shows your token once and provides a downloadable Python example. No operator invitation is needed. Save the token privately as `CHART_DOCTOR_API_TOKEN`.

If you lose your token, verify the same email again to replace it. The previous token stops working immediately. Your existing jobs, quota and idempotency keys stay with the account, so replacements do not reset your allowance. To revoke a token, use the button while it is displayed or send `DELETE /api/v1/access/token` with its bearer authorization. Email-code delivery uses the site's existing limits and ten-minute expiry; each code can issue only one token.

## Submit, poll and download

Use HTTPS. Keep credentials in environment variables rather than scripts or report files.

```python
import os
import time
import uuid
import requests

base = "https://dataviz.karthiks.co"  # Replace with the deployed site URL.
headers = {"Authorization": f"Bearer {os.environ['CHART_DOCTOR_API_TOKEN']}"}
submission_headers = {
    **headers,
    "X-OpenRouter-Key": os.environ["OPENROUTER_API_KEY"],
    "Idempotency-Key": uuid.uuid4().hex,
}
# Retain submission_headers across network retries of this same submission.
with open("chart.png", "rb") as chart:
    response = requests.post(
        f"{base}/api/v1/jobs", headers=submission_headers,
        files={"chart": ("chart.png", chart, "image/png")},
        data={"prompt": "Make the comparison easier to read"}, timeout=60,
    )
response.raise_for_status()
job = response.json()
deadline = time.monotonic() + 1800
while job["status"] in {"queued", "processing"}:
    if time.monotonic() > deadline:
        raise TimeoutError(f"Job still running: {job['job_id']}")
    time.sleep(3)
    response = requests.get(base + job["status_url"], headers=headers, timeout=30)
    response.raise_for_status()
    job = response.json()
if job["status"] != "ready":
    raise RuntimeError(job.get("error") or "Repair failed to start")
response = requests.get(base + job["download_url"], headers=headers, timeout=60)
response.raise_for_status()
with open("improved.png", "wb") as output:
    output.write(response.content)
print(job["result_kind"], job.get("error"), job.get("usage"))
```

`POST /api/v1/jobs` accepts PNG/JPEG up to 8 MB and instructions up to 5,000 characters. It returns HTTP 202 and a job record. `GET /api/v1/jobs/{id}` returns status; `GET /api/v1/jobs/{id}/chart` downloads the delivered PNG. Polling and download require only the Chart Doctor token.

A required `Idempotency-Key` prevents repeated submissions from launching another job. Reuse it when retrying a network request, with the same image and instructions. Different input with the same key returns 409. A failed launch remains recorded; use a new key to request a new attempt. Idempotency lasts for the job's 24-hour retention period.

The default allowance is 20 submissions per rolling 24 hours and one active job per client, with at most four active API jobs overall. Busy capacity or exhausted allowance returns 429. Jobs and images expire after 24 hours. Credentials are stored in a private file until the isolated worker reads and deletes it; credentials waiting longer than one hour are removed by maintenance. Worker finalization also removes credentials. They are never included in job records, responses, command arguments, or Google archives. BYOK jobs bypass the website's email verification and Google analytics/archive flow.

`result_kind` distinguishes `repaired`, `kept_original`, `original_fallback`, and `best_available`. A ready result can be the original when repair fails or the image is unsupported; check `error` and `result_kind` before inserting it into a report. Review defects do not suppress the best available chart. `usage` reports calls, tokens, elapsed seconds and provider-reported USD cost; cost can be null when providers omit it. No extra narration call is made.

## API contract

Base URL: `https://dataviz.karthiks.co`. Use HTTPS. No cookies are needed for job requests.

| Operation | Method and path | Required headers | Body / response |
|---|---|---|---|
| Submit | `POST /api/v1/jobs` | `Authorization: Bearer <Chart Doctor token>`, `X-OpenRouter-Key: <OpenRouter key>`, `Idempotency-Key: <stable request ID>` | Multipart form: `chart` is a PNG/JPEG file; `prompt` is optional text. HTTP 202 with the job record. Let the HTTP library set the multipart content type and boundary. |
| Poll | `GET /api/v1/jobs/{job_id}` | `Authorization: Bearer <Chart Doctor token>` | HTTP 200 with the job record. |
| Download | `GET /api/v1/jobs/{job_id}/chart` | `Authorization: Bearer <Chart Doctor token>` | HTTP 200, `image/png`, when ready; HTTP 409 before delivery. |
| Revoke own token | `DELETE /api/v1/access/token` | `Authorization: Bearer <Chart Doctor token>` | HTTP 200 revokes the token. Verify email again to get a replacement. |

`Idempotency-Key` must be 1–128 characters. Instructions are limited to 5,000 characters. Uploads are limited to 8 MB. Poll every three seconds, with a bounded overall deadline; the reference uses 30 minutes. A client timeout does not cancel the server job: resume polling the recorded status URL rather than submitting another job.

Every submit or poll response has these fields:

| Field | Meaning |
|---|---|
| `job_id` | Opaque string identifying the job. |
| `status` | `queued`, `processing`, `ready` or `failed`. Poll the first two; download only when ready. `failed` means the worker could not start. |
| `status_url` | Relative URL for polling; prefix it with the base URL. |
| `download_url` | Relative PNG URL when ready, otherwise null. |
| `expires_at` | Unix timestamp in seconds; job access expires after 24 hours. |
| `result_kind` | Null while pending; `repaired`, `kept_original`, `original_fallback` or `best_available` after delivery. |
| `error` | Null or a public failure message. A ready fallback can still have an error. |
| `review` | Null or the engine's review verdict; a delivered repair may retain defects. |
| `usage` | Null or an object containing `cost` (USD, possibly null), `seconds`, `calls`, `input_tokens` and `output_tokens`. |

HTTP errors generally use `{"detail": "message"}`. Validation errors may use a structured `detail` array; handle both without logging credentials.

| HTTP status | Client action |
|---|---|
| 400 / 413 / 422 | Correct the image, file size, instructions or required headers before retrying. |
| 401 | Correct the Chart Doctor token. This is distinct from OpenRouter failures, which can yield a ready original fallback with an error. |
| 404 | Job is unknown, expired or belongs to another account. Do not silently create another paid job. |
| 409 | For submission, the idempotency key belongs to different input. For download, the chart is not ready; keep polling. |
| 429 / 503 / network failure | Back off with a bounded retry budget. Retry the same submission with its original idempotency key and complete input; retry polling with the saved URL. |

Idempotency covers duplicate submission, not automatic repair retries. For a recorded failed launch or a completed fallback, a new requested repair needs a new idempotency key and may incur additional model charges. Never automatically replace a key just because a response or download timed out.

## Operator notes

Self-service client records live in the existing private SQLite database. They store email, stable account identifier, token hash, enabled state and daily allowance, never the plaintext token. Disabling an account prevents token replacement from re-enabling it. Existing operator-issued tokens in `api-clients.json` remain valid; the CLI remains available for those legacy invitations but is not needed for signup.

The API uses the existing `chart-doctor-repair@.service` and release pinning. No new service or model stage is required. API capacity limits are separate from browser quotas; both share server resources. This beta does not offer automatic retries, arbitrary models, webhooks, report parsing or billing. Account records remain until deleted; see the [privacy policy](https://dataviz.karthiks.co/privacy#api-access).
