API

Programmatic access for clients who integrate by code instead of the dashboard. Create a project, push a batch of items, poll for status and results, and optionally receive a signed webhook when a clinician-reviewed batch is delivered.

You set the purpose of a project and it decides the deliverable you get back. Three purposes, set with purpose in your config (defaults to evaluate):

  • evaluate: each item carries a model output; you get a model-performance scorecard — accuracy, per-class metrics, critical misses.
  • label: you get your data back labelled, plus a summary — class distribution, coverage, agreement.
  • create: you get new data produced for you — gold answers, preference pairs, or ratings, plus a coverage/agreement summary.

Base URL

https://api.senebiclabs.com/api/v1/project
Quickstart

From zero to results

The whole flow in four calls. First, get a key, then set it in your shell:

BASE="https://api.senebiclabs.com/api/v1/project"
KEY="your_api_key"

1 · Create a project

curl -s -X POST "$BASE/projects" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{ "name": "My eval", "eval_config": { ... } }'

Returns a project_id. Full task config in Create a project.

2 · Push items

curl -s -X POST "$BASE/ingest" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: batch-1" \
  -d '{ "project_id": "...", "items": [ ... ] }'

3 · Poll for results

curl -s "$BASE/results?project_id=..." -H "Authorization: Bearer $KEY"

Clinicians review, then status becomes delivered with the report and reviewed items. Prefer a push? Register a webhook and we call you, signed.

Authentication

Bearer API key

Every request carries your API key as a bearer token:

Authorization: Bearer <YOUR_API_KEY>

Get your key at senebiclabs.com/developers: verify your email and create one in seconds. Keys are shown once, tied to your account, and you can revoke any of them there at any time.

Then create a project with POST /projects (below) and you get a project_id to push items to. One key can create and drive many projects.

Endpoint

POST/projectsCreate a project

Create a project and get back a project_id to push items to.

Start from a template (recommended)

Pick what you want to achieve and we build the project for you — no config to author. List the outcomes with GET /templates:

  • model_evaluation — grade your model’s outputs → accuracy + safety scorecard
  • data_labeling — your data back, labelled → labelled dataset + summary
  • rlhf_preference — pick the better of two responses → preference pairs for RLHF
  • gold_answers — write the ideal answer → gold dataset for fine-tuning
  • case_review — judge whether AI helped or hurt on full cases → audit dataset + impact distribution
  • benchmark_creation — author challenging test cases → an evaluation benchmark
  • adversarial_prompts — write probes that expose model gaps → a red-teaming test set
  • fact_checking — highlight errors in an answer, rewrite it, cite a source → accuracy + a corrections dataset
  • dialogue_creation — author realistic patient-clinician dialogues → synthetic training data
  • response_ranking — rank two answers on accuracy/empathy/clarity/safety → preference pairs with per-axis scores

Create from one, supplying your own classes (label set) where it applies:

curl -X POST "$BASE/projects" \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{
    "name": "Triage model eval",
    "template": "model_evaluation",
    "classes": ["Routine", "Urgent", "Emergency"],
    "webhook_url": "https://your-app.com/hooks/senebiclabs"
  }'

That is all most projects need. The rest of this section is the advanced path — authoring a full config yourself.

Tune a template to your own rubric. GET /templates also returns each template’s full eval_config. Take the closest one, edit it to fit your exact task (add rating axes, change fields or context), and submit it as a custom eval_config below instead of template — so you start from a working, validated config, not a blank page.

Custom config (advanced)

Define your own task from scratch:

curl -X POST "$BASE/projects" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Clinical response evaluation",
    "eval_config": {
      "title": "Clinical response review",
      "purpose": "evaluate",
      "schema": {
        "input": "text",
        "context": [
          { "key": "scenario",   "label": "Patient message" },
          { "key": "prediction", "label": "Model response" }
        ],
        "classes": ["Routine", "Urgent", "Emergency"],
        "case_id_field": "case_id",
        "fields": {
          "verdict":       { "type": "single", "options": ["Correct", "Incorrect", "Partial"], "required": true },
          "correct_label": { "type": "from_classes", "visible_when": "verdict!=Correct" },
          "critical_miss": { "type": "structured" },
          "notes":         { "type": "text" }
        }
      }
    },
    "webhook_url": "https://your-app.com/hooks/senebiclabs"
  }'

Response

{
  "ok": true,
  "project_id": "fc64fb22-...",
  "webhook_secret": "a28e0736cb92..."
}

Save the webhook_secret. It is returned once, only when you register a webhook_url, and is used to verify webhook authenticity (see Webhooks). Treat it like a password.

This example is an evaluation project: each item carries a prediction, clinicians return a verdict of Correct, Incorrect, or Partial, and the report scores accuracy. For a creation project, omit prediction and setfields to the labels you want produced; the results come back as content-and-label pairs with no scorecard.

Endpoint

POST/ingestPush items

Send a batch of items (for example, conversations). Each item is a JSON object whose fields match your task config. We tell you the exact fields at setup.

curl -X POST "$BASE/ingest" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: batch-2026-08-12-001" \
  -d '{
    "project_id": "YOUR_PROJECT_ID",
    "items": [
      { "case_id": "case_001", "scenario": "patient message...", "prediction": "Routine" },
      { "case_id": "case_002", "scenario": "patient message...", "prediction": "Urgent" }
    ]
  }'

Idempotency

Send an Idempotency-Key header with each batch. If a request times out and you retry with the same key, we recognise it and skip the insert, so a retry never creates duplicates. A repeated key returns:

{ "ok": true, "message": "Batch already ingested (idempotent)." }

Use a fresh key per distinct batch. Without a key, each call appends its items, so two identical calls would create duplicates.

Response

{ "ok": true, "message": "Ingested 2 items." }

Bulk (data in your storage)

For large volumes, don’t push the data through the API at all. Leave it in your storage (e.g. S3) and send a manifest_url instead of items. A manifest is a JSONL file where each line is one item.

curl -s -X POST "$BASE/ingest" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{
    "project_id": "...",
    "source": { "manifest_url": "https://your-bucket.s3.../manifest.jsonl", "sample": 1000 }
  }'

The data itself never passes through the API and never leaves your storage — it is read directly from your bucket, so any volume works. Poll results as usual.

source.mode picks what gets reviewed. "sample" (default) reviews a representative random sample (default 1000) — best for evaluating a model’s quality without labeling everything. "all" reviews every item — best for labeling a full dataset; for very large sets we agree a volume and cadence up front.

  "source": { "manifest_url": "https://your-bucket.s3.../manifest.jsonl", "mode": "all" }
Endpoint

GET/resultsPoll status and results

Clinician review is done by people, so results are not instant. Poll this endpoint.status moves through received, in_review, delivered, and total / done show progress. Only delivered includes the report and items.

curl "$BASE/results?project_id=YOUR_PROJECT_ID" \
  -H "Authorization: Bearer $API_KEY"

While in review

{ "ok": true, "project_id": "...", "status": "in_review", "total": 200, "done": 142 }

When delivered

{
  "ok": true,
  "project_id": "...",
  "status": "delivered",
  "total": 200,
  "done": 200,
  "report": {
    "accuracy": { "value": 0.8, "correct": 160, "assessable": 200 },
    "critical_misses": [ ... ],
    "per_class": { ... },
    "qa": { "mean_agreement": 0.86, "reviewers": 3, "disagreements": 12 }
  },
  "items": [
    { "idx": 0, "content": { "case_id": "case_001", ... },
      "label": { "verdict": "Correct", ... }, "labeled_at": "..." }
  ]
}

Each item is reviewed by multiple licensed clinicians, and the qa block reports their mean agreement and how many items needed adjudication — so you can trust the numbers.

Scoring contract: to get the accuracy report, items must carry aprediction and your fields must use these exact names: verdict (Correct / Incorrect / Partial), correct_label (the corrected class for a wrong verdict), and critical_miss (a structured field that populates the report’s critical misses). A wrong verdict with no correct_label is excluded, never guessed. label and create projects skip scoring and return every reviewed item in items as a content-and-label pair.

Delivery

Webhooks optional, signed

If you registered a webhook_url, we POST it once when the batch is delivered, so you do not have to poll. The body is the same shape as the delivered GET /results response:

POST https://your-app.com/hooks/senebiclabs
Content-Type: application/json
X-Senebiclabs-Signature: sha256=<hex>

{
  "event": "results.delivered",
  "project_id": "...",
  "company": "Your Company",
  "report": { ... },
  "items": [ ... ]
}

Verify the signature

Every webhook carries an X-Senebiclabs-Signature header. It is an HMAC-SHA256 of the exact request body, keyed with your webhook_secret. Recompute it and compare in constant time before you trust the payload. This proves the request came from us and was not altered in transit.

Compute over the raw request bytes, before any JSON parsing. Parsing and re-serialising can change the bytes and break the check.

import hmac, hashlib

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header or "")

# FastAPI example
@app.post("/hooks/senebiclabs")
async def hook(request: Request):
    raw = await request.body()
    sig = request.headers.get("X-Senebiclabs-Signature", "")
    if not verify(raw, sig, WEBHOOK_SECRET):
        raise HTTPException(status_code=401)
    payload = json.loads(raw)   # trusted from here
    ...

Return 2xx to acknowledge. One call is made per delivery (no automatic retries), so keep polling GET /results as the source of truth if delivery is critical.

Reference

Task config

The eval_config defines what clinicians see and fill in. Key fields:

  • purpose: evaluate (grade a model output), label (categorise / annotate data), or create (produce gold answers, preferences, or ratings). Defaults to evaluate; it sets the reviewer workflow and the deliverable.
  • instructions: your rubric, shown to clinicians at the top of every task — what to evaluate, the standard, what counts as an error, edge cases. Optional, but it’s the single biggest lever on answer quality and reviewer agreement. Use line breaks to separate points. (Templates ship with a starter rubric you can tune.)
  • adjudicate: true holds any item where reviewers disagree for a senior reviewer to resolve, instead of shipping the majority vote. Recommended for judgment work; the judgment templates set it for you. Optional (default false).
  • auto_deliver: by default a finished batch is held for a human sign-off before it’s released to you (status stays in_review until then). Set true for hands-off delivery the moment every item is done. Optional (default false).
  • input: text (shows the context fields), image (each item needs an image URL), or audio / video (each item needs an audio / video URL; a clinician plays it, streamed straight from your storage).
  • context: for text tasks, which data keys to show the clinician, in order.
  • classes: the label set used by from_classes and structured fields.
  • case_id_field: which item field ties a result back to your own record.

fields is a map of what the clinician fills. Each has a type:

  • single: choose one of options.
  • from_classes: choose one of the project classes.
  • structured: yes or no, plus which finding (from classes).
  • scale: a rating from 1 to max.
  • flag: a single checkbox.
  • text: free-text notes (rows sets the box height for long-form).
  • spans: highlight text in the model output and tag each span with one of options (text input only).

Any field can add required: true, visible_when: "field!=value", and hint: "..." (a one-line note shown under the field’s label to guide the clinician).

Reference

Errors and notes

  • Errors: 401 invalid or missing key, 403 project not on this key, 422 invalid config or items missing a required field, 503 service unavailable.
  • Idempotency: send an Idempotency-Key header per batch so retries are safe. Without one, each /ingest appends its items.
  • Content shape is up to you as long as it matches the configured task. For text review, typically prompt plus output. Add case_id to tie results back to your records.
Questions? senebiclabs@gmail.com