# Dopp API

Dopp is a drop-in proxy for any Jev-compatible decision API (`POST /v1/systemone`: TypeSafe's Jev, Kev, GLiNER, or your own endpoint). Send the same request you send to Jev; get the same response shape back. Each key belongs to a **project**. The project's routing rules decide who answers: Jev, a model trained on the project's own examples, or an endpoint you run. Every request is recorded as an example the model can learn from.

Base URL: `https://dopp.sh`

## Set up from an agent

One call creates an account with a working key; no sign-up form. The person attaches it to themselves later by opening the claim link.

```
POST https://dopp.sh/agent/start
{"name": "my-app", "code": "<invite code, optional>"}
→ {"key": "us_…", "endpoint": "https://dopp.sh/v1/systemone", "claim_url": "https://dopp.sh/claim/…", "spendable_usd": 1, "expires_at": "…"}
```

Until it is claimed the key is a guest: it works up to the no-card allowance, then answers 402 until the claim link is opened. Guests expire after 7 days. `GET /agent/status` with the key reports requests seen, spend left and whether it is claimed. Agent instructions: https://dopp.sh/skill.md

## Authentication

Every request carries a project key as a bearer token. Keys start with `us_` and are created on the model's Requests page, or by `POST /agent/start`. A key is shown once when created. Requests through a key are recorded in that key's project and follow that project's routing.

```
Authorization: Bearer us_...
```

You do not need a TypeSafe key. Calls to Jev go through Dopp's account by default and are metered from your credit. To be billed by TypeSafe directly instead, either add your key in Settings (stored encrypted, used only upstream) or send it per request as `x-jev-key: <your key>`, which is forwarded and never stored.

## Endpoint

`POST /v1/systemone`

Request body (identical to TypeSafe):

```json
{
  "model": "jev-latest",
  "state": "Board arrived with a cracked deck. I want a replacement, not a refund.",
  "questions": {
    "issue":  { "type": "choice", "instructions": "What is the main issue?",
                "criteria": { "damaged": "arrived broken", "wrong item": null, "late": null, "billing": null, "question": null } },
    "wants":  { "type": "choice", "instructions": "What does the customer want?",
                "criteria": { "replacement": null, "refund": null, "repair": null, "information": null } },
    "angry":  { "type": "noul",   "instructions": "Is the customer angry?" },
    "urgency":{ "type": "score",  "instructions": "How urgent is this?", "criteria": ["can wait", "this week", "today"] }
  }
}
```

- `state`: a string, object or array. Anything JSON.
- `questions`: an object of question id → question. Three types:
  - `choice`: pick one of `criteria` (option name → description or `null`).
  - `noul`: yes/no. Returns a probability of yes.
  - `score`: an ordered scale; `criteria` is the list of levels, lowest first.

Response:

```json
{
  "model": "jev-1.13.0",
  "answers": {
    "issue":   { "type": "choice", "choice": "damaged", "probabilities": { "damaged": 0.97, "wrong item": 0.01, "late": 0.01, "billing": 0.0, "question": 0.01 } },
    "wants":   { "type": "choice", "choice": "replacement", "probabilities": { "replacement": 0.95, "refund": 0.03, "repair": 0.02, "information": 0.0 } },
    "angry":   { "type": "noul",   "noul": 0.81 },
    "urgency": { "type": "score",  "score": 1.4, "probabilities": { "0": 0.1, "1": 0.4, "2": 0.5 } }
  },
  "usage": { "input_tokens": 118, "output_tokens": 0 }
}
```

Dopp adds two fields to the response:

```json
{
  "model": "understudy/ab12cd34-v3",
  "understudy": { "served": "model", "rule": "r2", "shadow": ["jev"] }
}
```

- `model`: who answered. `jev-…` for Jev, `understudy/<project>-v<N>` for the project's model, `endpoint:<id>` for your own endpoint.
- `understudy.served`: the target that answered (`jev`, `model` or `endpoint:<id>`); `rule`: the routing rule that decided; `shadow`: targets that also ran and were recorded but not returned.

### Choosing a target per request

Send `x-understudy-target: jev` (or `model`, or `endpoint:<id>`) to pick who answers one request. It is honoured only while the project's "header" routing rule is on; otherwise it is ignored.

Errors: `401` with `{"error": "..."}` for a missing, invalid or revoked key. Upstream errors from Jev are passed through with their status code.

## Examples

curl:

```bash
curl -s https://dopp.sh/v1/systemone \
  -H "Authorization: Bearer $UNDERSTUDY_KEY" \
  -H "content-type: application/json" \
  -d '{"state":"Trucks are loose, can you send new ones?","questions":{"wants":{"type":"choice","instructions":"What does the customer want?","criteria":{"replacement":null,"refund":null,"repair":null,"information":null}}}}'
```

Python:

```python
import os, requests

def decide(state, questions):
    r = requests.post("https://dopp.sh/v1/systemone",
                      headers={"Authorization": "Bearer " + os.environ["UNDERSTUDY_KEY"]},
                      json={"model": "jev-latest", "state": state, "questions": questions}, timeout=30)
    r.raise_for_status()
    return r.json()["answers"]
```

Node:

```js
const answers = await fetch("https://dopp.sh/v1/systemone", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.UNDERSTUDY_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({ model: "jev-latest", state, questions }),
}).then(r => r.json()).then(r => r.answers);
```

TypeSafe's own SDK works unchanged: set its base URL to `https://dopp.sh` and its API key to your project key.

## Projects, examples, model, routing

Everything below is in the dashboard; there is no API for it yet.

- **Project**: a name, keys, examples, one model and routing rules. Make one per product or use case.
- **Examples**: every request through a key is recorded (source `traffic`). You can also add examples by typing one, pasting a file (text, CSV, JSON, JSONL), pulling rows from a public dataset, or generating them. Jev labels each; you can correct any label, mark a question unsure, or exclude an example.
- **Held-out**: one in ten examples you add (not traffic) is kept aside and never trained on, so "agreement on your examples" is honest.
- **Model**: train any time you have at least one example. Each training makes a new version, measured on the held-out examples and, separately, on real traffic where it ran alongside Jev. The two numbers are never blended. Checkpoints can be downloaded.
- **Routing**: an ordered list of rules, first match wins:
  - `split`: send a percentage to each target.
  - `confidence_gate`: if the answer's lowest confidence is below a threshold, ask another target and return its answer.
  - `fallback`: on an error or timeout, use another target.
  - `shadow`: also run a target on a percentage of requests; record it, don't return it.
  - `cache`: identical state and questions return the same answer for a while.
  - `header`: honour `x-understudy-target`.

  A new project sends everything to Jev and shadows its model (once one exists), so you can compare before switching.

Keep questions and options stable. Changing an option's name makes a new question-set with no examples of its own.

## For agents

If you are an AI agent integrating Dopp on someone's behalf:

- Replace the TypeSafe base URL with the one above and the TypeSafe key with an Dopp project key. Nothing else changes.
- Never log the key. Treat it like an API secret.
- Read `model` in each response to know who answered. Don't add `x-understudy-target` unless the user asks for it.
- Do not rename options once a project's model is trained on them; a changed question-set has no examples yet.
- There is no API to create keys, train, or change routing yet. Those are in the dashboard.

## Limits

- One request at a time per key is fine; there is no rate limit yet beyond Jev's own.
- State plus questions must fit in about 8,000 tokens.
- Choice questions with more than about 50 options learn poorly; keep routing those to Jev.
