API reference
The base URL is the host (http://127.0.0.1:8001 by default); every API route sits
under the API major /v1. Every response carries an X-Request-ID header for log
correlation. Callers identify themselves with their own
caller key in an X-API-Key: <value> header. Who may
call which route:
| Route | Who |
|---|---|
GET /health, GET /v1/evaluators |
Open |
POST /v1/connection-profiles, DELETE /v1/connection-profiles/{name}, PUT /v1/evaluator-backends/{backend}/connection-profile, GET /v1/evaluators/requests |
The platform-admin caller only |
GET /v1/connection-profiles, GET /v1/connection-profiles/{name}, GET /v1/evaluator-backends, POST /v1/evaluators/requests |
Any known caller |
POST /v1/score-jobs, GET /v1/score-jobs/{job_id} |
Warning phase: a known caller is identified, and a call without a known key is still accepted and logged. A key becomes required from an enforcement date announced in the changelog |
A request without a key, or with a key no caller has, gets HTTP 401
Invalid or missing API key.; a pod key on an admin route gets HTTP 403
This route needs the platform-admin caller. When CALLER_KEY_VAULT_URL is
unset (local development and tests) every route is open. See
Authentication.
Versions
The API has one supported major, v1. Every API route sits under it
(/v1/...); the base URL is the host alone. /health, /docs, /redoc and
/openapi.json are unversioned. The API major
policy (supported majors, retirement window, what counts as a breaking
change) is written once in
Service API versions.
The same paths without the /v1 prefix (for example /evaluators) remain as
deprecated aliases: the same operations, deprecated on 2026-11-06 and removed no earlier
than 2027-02-04. Every response on an alias carries three headers:
Deprecation: @1793923200
Sunset: Thu, 04 Feb 2027 00:00:00 GMT
Link: <https://blue-sand-0fe957b03.7.azurestaticapps.net/docs/scoring-service/04-api-reference/>; rel="deprecation"
Deprecation is the deprecation instant as a Unix timestamp (RFC 9745),
Sunset is the earliest removal date (RFC 8594), and Link points at this
API reference. Responses under /v1 carry none of them.
/docs shows both mounts, with the aliases marked deprecated. The request log
line carries api_version (v1, unversioned, or none for paths such as
/health) and user_agent, so traffic still on the aliases is visible
before they are removed.
Base URLs and interactive docs
| Environment | Base URL |
|---|---|
| Local | http://127.0.0.1:8001 |
| Non-prod deployed | https://scoring-service.bravecliff-5469885d.swedencentral.azurecontainerapps.io |
The deployed service sits behind the VPN. Both environments serve the generated OpenAPI documentation, which is always in step with the running build in a way this page cannot promise to be:
| Path | What it is |
|---|---|
/docs |
Swagger UI, with a request form for every route |
/redoc |
The same schema as a reference page |
/openapi.json |
The raw schema, for generating a client |
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET |
/health |
no | Liveness check |
GET |
/v1/evaluators |
no | Return the full evaluator catalogue |
POST |
/v1/score-jobs |
no | Submit items and predictions as a scoring job; returns a job ID immediately |
GET |
/v1/score-jobs/{job_id} |
no | Return one scoring job's status and, once complete, its result |
POST |
/v1/connection-profiles |
yes | Register or replace a connection profile |
GET |
/v1/connection-profiles |
yes | List every connection profile, without their credentials |
GET |
/v1/connection-profiles/{name} |
yes | Fetch one connection profile, without its credential |
DELETE |
/v1/connection-profiles/{name} |
yes | Remove a connection profile |
GET |
/v1/evaluator-backends |
yes | Read which connection profile each evaluator backend uses |
PUT |
/v1/evaluator-backends/{backend}/connection-profile |
yes | Point an evaluator backend at a connection profile |
POST |
/v1/evaluators/requests |
yes | Submit a named custom rubric for review |
GET |
/v1/evaluators/requests |
yes | List pending custom-rubric submissions |
GET /health
Returns {"status": "ok"}. Used for local and cloud liveness checks.
GET /v1/evaluators
Returns an EvaluatorCatalogueResponse: a list of entries, each with
framework, evaluator, scope, raw_scale, and supported_params. See
01-evaluator-catalogue.md for the full contents
today and what each field means.
POST /v1/score-jobs
Submits a batch of predictions for scoring as a scoring job
and returns immediately — it does not wait for scoring to finish. Accepts a
ScoreRequest:
| Field | Type | Notes |
|---|---|---|
items |
list[EvaluationItem] |
Trusted evaluation items (item_id, scope, dataset_name, input, expected_output, metadata) |
predictions |
list[PredictionRecord] |
One prediction per item (item_id, scope, dataset_name, predictor_name, input, expected_output, prediction, optional trace_id/observation_id) |
evaluators |
dict[scope, ScopeEvaluatorConfig] |
Per-scope {metric_name: MetricConfig} map; MetricConfig carries framework, evaluator, enabled, threshold, direction, model, evaluator_params |
gate |
GateConfig |
mode (currently always "all_metrics") and minimum_item_count; defaults to mode="all_metrics", minimum_item_count=1 |
scopes |
list[str] \| null |
Explicit scope list; defaults to every distinct scope present in items |
A prediction whose item_id has no matching item, or a metric naming an
unknown (framework, evaluator) pair, or a metric whose backend has no
connection profile assigned all return HTTP 400 with a detail message
before any job is created — this validation runs synchronously, so a
structurally bad request never occupies a job slot. See
01-evaluator-catalogue.md and
02-connection-profiles.md for the exact failure
conditions.
On success, returns HTTP 202 with a ScoreJobSubmitted:
| Field | Type | Notes |
|---|---|---|
job_id |
str |
Pass to GET /v1/score-jobs/{job_id} to check status and fetch the result |
status |
str |
Always "pending" at submission time |
The actual scoring then runs as a background task: judge calls across the
batch run concurrently, bounded by OFFLINE_SCORING_MAX_CONCURRENT_JUDGE_CALLS
(see 06-deployment.md), rather than
one at a time. See getting-started.md
for a complete routing-only submit-and-poll walkthrough.
When a prediction carries prediction["_trace_fetch_failed"]: true (set by a
pod's predictor when it could not retrieve the underlying trace), every
MetricResult for that prediction gets a suffix appended to its
justification noting that the score was computed from empty prediction
data.
GET /v1/score-jobs/{job_id}
Returns a ScoreJobResult for one job, or HTTP 404 if job_id is unknown:
| Field | Type | Notes |
|---|---|---|
job_id |
str |
Echoes the requested ID |
status |
str |
"pending", "running", "completed", or "failed" |
submitted_at |
str |
ISO 8601 UTC timestamp |
started_at |
str \| null |
Set once the job leaves pending |
finished_at |
str \| null |
Set once the job reaches completed/failed |
completed_evaluations |
int \| null |
Judge calls finished so far |
total_evaluations |
int \| null |
Total judge calls this job will run |
result |
ScoreResponse \| null |
Set once status is "completed" |
error |
str \| null |
Set once status is "failed" |
A completed job's result is a ScoreResponse:
| Field | Type | Notes |
|---|---|---|
metric_results |
list[MetricResult] |
One entry per (item, metric) pair that ran |
aggregate_results |
list[AggregateMetricResult] |
One entry per (scope, metric_name) pair |
gate_result |
GateResult |
passed, mode, minimum_item_count, total_items, failing_metrics |
item_count_by_scope |
dict[str, int] |
Item counts per requested scope |
Job records are persisted under OFFLINE_SCORING_STATE_DIR, so a poll can
land on either Container Apps replica and still see the same job.
POST /v1/connection-profiles
Registers or replaces an azure_openai or foundry_decision connection
profile (registering a name that already exists overwrites it). Returns the
stored profile without its credential. See
02-connection-profiles.md.
GET /v1/connection-profiles
Returns every registered profile as a list.
GET /v1/connection-profiles/{name}
Returns one profile, or HTTP 404 if name is not registered.
DELETE /v1/connection-profiles/{name}
Removes a profile and any backend assignment that referenced it. Returns
{"deleted": "<name>"}, or HTTP 404 if name was never registered.
GET /v1/evaluator-backends
Returns the backend to profile-name map, e.g.
{"deepeval_llm_judge": "azure-default"}. A backend with no profile assigned
is absent from the map, which is also what makes a scoring request using it
fail with HTTP 400.
PUT /v1/evaluator-backends/{backend}/connection-profile
{backend} is an evaluator backend key such as deepeval_llm_judge or
decision_llm_judge. Body:
{"connection_profile_name": "<registered profile name>"}. Returns
{"backend": "<backend>", "connection_profile_name": "<name>"}. Returns HTTP
400 if backend is not a known backend key or the profile is not registered,
and HTTP 422 if the body carries any other field.
One profile can serve several backends; a backend has at most one profile, so repeating the call replaces the previous assignment rather than accumulating.
POST /v1/evaluators/requests
Submits an EvaluatorRegistrationRequest (name, criteria, scale,
input_fields, output_fields, expected_output_fields, model,
submitted_by) for eval-core-team review. Writes a pending submission file
and returns the resulting AcceptedEvaluatorRecord (HTTP 201).
- Returns HTTP 400 if
nameis"inline"(reserved). - Returns HTTP 409 if
nameis already accepted (committed tocustom_evaluators/accepted/). - Re-submitting a name that is still pending overwrites the pending file.
See 03-custom-rubric-and-registration.md.
GET /v1/evaluators/requests
Returns every pending submission as a list of AcceptedEvaluatorRecord
objects, for the eval core team to review before accepting.