VVDex

Quickstart · REST API

The REST API

Everything the web app does is a JSON call under https://vvdexops.com/api/v1. The full schema is at /api/openapi.json (OpenAPI; load it into any OpenAPI tool).

Authenticate

Create a key in the web app under Settings → API keys and send it on every request:

shell
$ export VVDEX_API_KEY=vvx_…
$ curl -s https://vvdexops.com/api/v1/me -H "Authorization: Bearer $VVDEX_API_KEY"

A key acts for its workspace. It can read and run everything in it, but it cannot create API keys or delete the account; those need a signed-in browser.

Refer to a model

Wherever a model is expected, send {"connection_id": "…", "model": "…"}. GET /models lists the ones you saved.

1 · Connect and check a model

shell
$ curl -s https://vvdexops.com/api/v1/connections \
    -H "Authorization: Bearer $VVDEX_API_KEY" -H "Content-Type: application/json" \
    -d '{"provider": "openai", "label": "OpenAI", "api_key": "sk-…"}'
# → {"id": "con_…", "provider": "openai", "key_hint": "…abcd", "has_key": true, …}

$ curl -s https://vvdexops.com/api/v1/connections/con_…/check \
    -H "Authorization: Bearer $VVDEX_API_KEY" -H "Content-Type: application/json" \
    -d '{"model": "gpt-4.1"}'
# → {"verdict": "ok" | "warn" | "fail", "findings": [{"level", "check", "detail", "kind"}], "model"}

The key is encrypted on arrival and never returned; later reads show only its last four characters. GET /providers lists every provider id; use custom with a base_url for any OpenAI-compatible address, or http for an app.

2 · Compare in the playground

shell
$ curl -s https://vvdexops.com/api/v1/playground \
    -H "Authorization: Bearer $VVDEX_API_KEY" -H "Content-Type: application/json" \
    -d '{"messages": [{"role": "user", "content": "Name three prime numbers."}],
         "models": [{"connection_id": "con_…", "model": "claude-sonnet-4-5"},
                    {"connection_id": "con_…", "model": "gpt-4.1"}]}'
# → {"id": "pg_…", "results": [{"model", "ok", "text", "latency_ms", "input_tokens",
#                               "output_tokens", "cost_usd", "finish_reason", "error"}, …]}

cost_usd is null when the price is unknown. A failed call has ok: false and an error.kind such as auth, timeout or rate_limit.

3 · Start an evaluation

shell
$ curl -s https://vvdexops.com/api/v1/evals \
    -H "Authorization: Bearer $VVDEX_API_KEY" -H "Content-Type: application/json" \
    -d '{"name": "nightly incident triage",
         "source": {"forge": "vvdex.ops.gateway-incident-1", "attempts": 3},
         "models": [{"connection_id": "con_…", "model": "gpt-4.1"}]}'
# → {"id": "…", "status": "queued"}

GET /forge/exams lists the exams and their limits. For your own data, use "source": {"dataset_id": "…"} and add "graders": [{"type", "params", "weight"}]; GET /graders lists them. Optional: judge (a model reference), system_prompt, params (temperature, max_tokens) and repeats (1 to 5).

4 · Read the result

shell
$ curl -s https://vvdexops.com/api/v1/evals/RUN_ID -H "Authorization: Bearer $VVDEX_API_KEY"
# status, progress {done, total}, and per model: n_scored, n_passed, pass_rate,
# wilson_low, wilson_high, mean_score, errors {kind: count}, cost_usd, latency_ms {p50, p95}, judged

$ curl -s "https://vvdexops.com/api/v1/evals/RUN_ID/results?status=failed&limit=20" \
    -H "Authorization: Bearer $VVDEX_API_KEY"
$ curl -s "https://vvdexops.com/api/v1/evals/compare?a=RUN_A&b=RUN_B" -H "Authorization: Bearer $VVDEX_API_KEY"
$ curl -s https://vvdexops.com/api/v1/evals/RUN_ID/receipt -H "Authorization: Bearer $VVDEX_API_KEY" > receipt.json

Errors are counted per kind and left out of n_scored, so they never lower a pass rate.

Endpoints

AreaEndpoints
AccountGET /me · GET /usage · GET /plans · GET /audit
ModelsGET /providers · GET|POST /connections · DELETE /connections/{id} · POST /connections/{id}/check · GET|POST /models · DELETE /models/{id}
PlaygroundPOST /playground · GET /playground · POST /playground/{id}/share
DatasetsGET|POST /datasets · GET|DELETE /datasets/{id} · POST /datasets/{id}/rows · POST /datasets/{id}/upload · PATCH|DELETE /datasets/{id}/rows/{row_id} · GET /graders
EvaluationsGET|POST /evals · GET /evals/{id} · GET /evals/{id}/results · POST /evals/{id}/cancel · GET /evals/compare · POST /evals/{id}/share · GET /evals/{id}/receipt · GET /evals/{id}/export.csv · POST /evals/{id}/publish
Judge calibrationPOST /evals/{id}/results/{result_id}/label · GET /evals/{id}/agreement
Forge examsGET /forge/exams · GET /leaderboards · GET /leaderboards/{exam}
What we testedGET /research/campaigns · GET /research/campaigns/{id} · GET /research/models · GET /research/exams/{exam}
PublicPOST /receipts/verify · GET /receipts/public-key · GET /status

Errors and limits

A refused request returns {"detail": "…"} in plain words, with the HTTP status:

  • 400 the input is not usable (the detail says why) · 422 a field has the wrong type
  • 401 no key, or a key that is wrong or revoked · 403 the action needs a signed-in browser, or your role is read-only
  • 404 not found, or not in your workspace
  • 429 a rate limit or a plan limit was reached; for a plan limit the detail names it and says when it resets

Rate limits per workspace: 300 playground runs an hour, 60 new connections an hour, 120 connection checks an hour. Workspace limits are on the limits page.