API reference
The base URL is the host (http://127.0.0.1:8002 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. Routes marked auth require a known key; the
binding and sync-job routes are in a warning phase. See
Authentication below.
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 /bindings) 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/review-sync-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:8002 |
| Non-prod deployed | https://review-sync-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/bindings |
warning phase | List all registered review-sync bindings |
GET |
/v1/bindings/{binding_id} |
warning phase | Fetch one binding |
PUT |
/v1/bindings/{binding_id} |
warning phase | Create or replace one binding |
POST |
/v1/bindings/validate |
warning phase | Validate a binding definition without storing it |
POST |
/v1/sync-jobs |
warning phase | Run one sync job immediately for a stored binding |
GET |
/v1/sync-jobs/{job_id} |
warning phase | Load one stored sync-job result |
POST |
/v1/langfuse-profiles |
yes | Register or replace a Langfuse connection profile |
GET |
/v1/langfuse-profiles |
yes | List every Langfuse profile, without their secret keys |
GET |
/v1/langfuse-profiles/{profile_id} |
yes | Fetch one Langfuse profile, without its secret key |
DELETE |
/v1/langfuse-profiles/{profile_id} |
yes | Remove a Langfuse connection profile |
Every /v1/langfuse-profiles route is auth-protected. Every /v1/bindings
route (including POST /v1/bindings/validate) and the sync-job routes are in a
warning phase, described under Authentication.
ReviewSyncServiceClient sends an X-API-Key header whenever it is given an
api_key; eval-review passes REVIEW_SYNC_API_KEY from the environment.
!!! note "POST /v1/sync-jobs is synchronous, unlike the scoring service's POST /v1/score-jobs"
Both services expose a -jobs noun and they do not behave the same way.
POST /v1/sync-jobs here runs the job and returns its terminal JobResult on
the same request. The scoring service's POST /v1/score-jobs returns 202
with a job id you then poll. Sync jobs are short and their result is what
the caller wants; scoring jobs wait on LLM judges and can outlast an HTTP
timeout.
GET /health
Returns {"status": "ok"}. Used for local and cloud liveness checks.
GET /v1/bindings
Returns a BindingListResponse: every stored binding, sorted by
binding_id.
GET /v1/bindings/{binding_id}
Returns one QueueToDatasetBinding, or HTTP 404 if binding_id is not
registered.
PUT /v1/bindings/{binding_id}
Creates the binding if binding_id is new, or fully replaces it if it
already exists. The path segment must match binding.binding_id in the
request body (HTTP 400 otherwise). The binding also runs through the same
semantic validation as POST /v1/bindings/validate before it is stored — a
binding that fails validation is rejected with HTTP 400 and never written.
Returns the stored binding. See
01-bindings.md.
POST /v1/bindings/validate
Runs the same semantic validation PUT /v1/bindings/{binding_id} runs, without
storing anything. Returns a BindingValidationResult: valid plus a list of
human-readable errors. See
01-bindings.md — Binding validation.
POST /v1/sync-jobs
Accepts a JobRequest (binding_id, dry_run, max_items, page_size,
persist_artifacts) and runs one sync job immediately. Returns a JobResult
(HTTP 404 if binding_id is not registered). See
02-sync-execution.md for the full execution flow and
outcome semantics.
GET /v1/sync-jobs/{job_id}
Returns one previously stored JobResult, or HTTP 404 if job_id was never
persisted.
POST /v1/langfuse-profiles
Registers or replaces a LangfuseConnectionProfile (registering a
profile_id that already exists overwrites it). Returns the stored profile
without its secret_key. See
03-langfuse-profiles-and-cleanup.md.
GET /v1/langfuse-profiles
Returns every registered profile as a list, each carrying profile_id,
base_url and public_key. secret_key is never returned, by any route:
it belongs to the pod that registered it, and the registry accepts a Key Vault
URI precisely so the value need not be handled in the clear.
GET /v1/langfuse-profiles/{profile_id}
Returns one profile in the same redacted shape, or HTTP 404 if profile_id is
not registered.
DELETE /v1/langfuse-profiles/{profile_id}
Removes a profile. Returns {"deleted": "<profile_id>"}, or HTTP 404 if
profile_id was never registered. Deleting a profile that a binding still
references does not fail the delete — the next sync job for that binding
fails at credential resolution instead.
Authentication
Every caller has its own caller key, sent as an X-API-Key: <value> header.
| Route | Who |
|---|---|
GET /health |
Open |
The four /v1/langfuse-profiles routes |
Any known caller |
Every /v1/bindings route, POST /v1/sync-jobs, GET /v1/sync-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, to a protected route gets
HTTP 401 Invalid or missing API key. What a profile points at is a pod's own
Langfuse project, so reading it is as privileged as changing it. Every request
log line carries caller=<id> (unknown for an unidentified call on a
warning-phase route, - when none applies).
When CALLER_KEY_VAULT_URL is unset every route is open and no caller is
identified, which is the local-development and test default. The service logs a
warning at startup when that happens, so an unprotected deployment says so in
its own logs rather than looking healthy. With it set, the service logs
Loaded N callers: <ids> at startup.