# Cases API Generated: 2026-11-02T05:41:48.007Z Source build: local Canonical docs: https://teammately.ai/docs --- id: public-api.cases title: Cases API summary: Read canonical project Cases and submit supported imports without confusing current project content with frozen benchmark membership. kind: api product_area: public_api status: stable updated: 2026-10-31 canonical: /docs/public-api/cases --- # Cases API ## Definition Under the organization/project prefix, current routes include: ```http GET /cases GET /cases/{caseId} POST /cases/imports GET /cases/imports/{importId} PUT /cases/{caseId}/reference-result POST /cases/reference-results ``` Paths above are suffixes of `/v1/organizations/{organizationKey}/projects/{projectId}`. ## Read behavior Case lists use `data.columns` and `data.rows`; detail uses `result`. Case IDs and dataset-record IDs remain their issued identities. Canonical content, schema version, derived search text, compatibility metadata and latest reference-output context are distinct fields. These project reads do not define membership of a fixed Benchmark Version. Use the benchmark `case-manifest` for exact frozen Cases used by an evaluation. ## Reference responses A Case reference-result operation is different from an external candidate Run. Reference-result replacement controls and outcomes do not authorize overwriting accepted benchmark Trial responses. ## Input contract Canonical Case content uses its declared input container and material blocks. Project Input Schema constrains future Case authoring. This router does not establish the old proposed customer `/input-schema` or `/cases/{caseId}/versions` endpoints. ## List and retrieve Cases ### Example: Read Cases ```sh curl --fail-with-body \ --header "Authorization: Bearer ${TEAMMATELY_API_KEY}" \ "${PROJECT_URL}/cases?limit=10&offset=0" # CASE_ID is a case_id returned in data.rows. curl --fail-with-body \ --header "Authorization: Bearer ${TEAMMATELY_API_KEY}" \ "${PROJECT_URL}/cases/${CASE_ID}" ``` ```python import os from urllib.parse import quote, urlencode from urllib.request import HTTPRedirectHandler, Request, build_opener class NoRedirect(HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): return None opener = build_opener(NoRedirect()) api_key = os.environ["TEAMMATELY_API_KEY"] url = os.environ["PROJECT_URL"] + "/cases" url += "?" + urlencode({"limit": "10", "offset": "0"}) headers = {"Authorization": "Bearer " + api_key} request = Request(url, headers=headers, method="GET") with opener.open(request) as response: print(response.read().decode("utf-8")) url = os.environ["PROJECT_URL"] + "/cases/" + quote(os.environ["CASE_ID"], safe="") headers = {"Authorization": "Bearer " + api_key} request = Request(url, headers=headers, method="GET") with opener.open(request) as response: print(response.read().decode("utf-8")) ``` ```typescript const apiKey = process.env.TEAMMATELY_API_KEY!; { const url = new URL(process.env.PROJECT_URL! + "/cases"); url.searchParams.set("limit", "10"); url.searchParams.set("offset", "0"); const response = await fetch(url, { method: "GET", redirect: "error", headers: { Authorization: `Bearer ${apiKey}`, }, }); if (!response.ok) throw new Error(`HTTP ${response.status}`); console.log(await response.text()); } { const url = new URL(process.env.PROJECT_URL! + "/cases/" + encodeURIComponent(process.env.CASE_ID!)); const response = await fetch(url, { method: "GET", redirect: "error", headers: { Authorization: `Bearer ${apiKey}`, }, }); if (!response.ok) throw new Error(`HTTP ${response.status}`); console.log(await response.text()); } ``` ```go package main import ( "context" "fmt" "io" "log" "net/http" "net/url" "os" ) func main() { if err := run(context.Background()); err != nil { log.Fatal(err) } } func run(ctx context.Context) error { client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } { target := os.Getenv("PROJECT_URL") + "/cases" query := url.Values{} query.Set("limit", "10") query.Set("offset", "0") target += "?" + query.Encode() request, err := http.NewRequestWithContext(ctx, "GET", target, nil) if err != nil { return err } request.Header.Set("Authorization", "Bearer " + os.Getenv("TEAMMATELY_API_KEY")) response, err := client.Do(request) if err != nil { return err } payloadBytes, readErr := io.ReadAll(response.Body) response.Body.Close() if readErr != nil { return readErr } fmt.Println(string(payloadBytes)) if response.StatusCode < 200 || response.StatusCode >= 300 { return fmt.Errorf("HTTP %d", response.StatusCode) } } { target := os.Getenv("PROJECT_URL") + "/cases/" + url.PathEscape(os.Getenv("CASE_ID")) request, err := http.NewRequestWithContext(ctx, "GET", target, nil) if err != nil { return err } request.Header.Set("Authorization", "Bearer " + os.Getenv("TEAMMATELY_API_KEY")) response, err := client.Do(request) if err != nil { return err } payloadBytes, readErr := io.ReadAll(response.Body) response.Body.Close() if readErr != nil { return readErr } fmt.Println(string(payloadBytes)) if response.StatusCode < 200 || response.StatusCode >= 300 { return fmt.Errorf("HTTP %d", response.StatusCode) } } return nil } ``` Configure `PROJECT_URL` with the [quickstart](/docs/public-api/quickstart). The list defaults to 10 rows, permits `limit` from 1 through 1,000 and uses nonnegative `offset`. Conditional reads use the returned ETag with `If-None-Match`. ## Write a reference response `PUT /cases/{caseId}/reference-result` accepts `output` and optional normalized `trajectory`. It returns `201` for a created reference or `200` for a replacement/unchanged reference, plus an ETag. `If-Match` supplies a conditional precondition. The batch operation requires `Idempotency-Key` and returns per-Case outcomes. See `ReferenceResultWrite` and `ReferenceResultBatchWrite` in the [OpenAPI](https://api.teammately.ai/v1/openapi.yaml) for replacement controls and batch fields. ## Related reference pages {% related-card-grid title="Related reference pages" %} - [Case Serialization](/docs/public-api/case-schema) - [Import Cases](/docs/public-api/case-imports) - [Submit External Evaluation Outputs](/docs/public-api/evaluation-outputs) {% /related-card-grid %} --- id: public-api.case-schema title: Case Serialization summary: Preserve current Case envelopes, canonical content, material blocks, compatibility projections, and separate reference-output context. kind: api product_area: public_api status: stable updated: 2026-10-31 canonical: /docs/public-api/case-schema --- # Case Serialization ## Definition The current Case record includes issued `case_id` and `dataset_record_id`, creation/update timestamps, canonical `content`, `content_schema_version`, derived `content_search_text`, `legacy_metadata` and available `latest_version` output context. Canonical content uses `schema_version: dataset_record.content.v1`, an input container and optional `case_materials`, `world_refs` and `capability_refs`. ## Canonical and rendered content The owning schema defines message or structured-object input and material-block alternatives. Do not flatten these into the earlier proposed `input.kind: plain_text` envelope or invent another public context object. A saved Case view is a rendered projection. It does not become another editable canonical payload. ## Materials and references Preserve declared material keys, artifact references and issued identities. Display paths and search text are not content authority. Source World references are separate from an executable `world_instance_ref`. A serialized field does not establish a World-management endpoint or durable provisioning workflow. ## Outputs and history Latest reference-output context can include an output and normalized trajectory. It is not the response of every candidate Run. Frozen benchmark Case identities and revisions come from the selected Version's manifest, not a current mutable project read. ## Field reference | Field | Meaning | | --- | --- | | `case_id`, `dataset_record_id` | Required issued identities | | `created_at` | Required creation timestamp | | `updated_at` | Update timestamp when returned | | `content` | Canonical content object or null | | `content_schema_version` | `dataset_record.content.v1` or null | | `content_search_text` | Derived search representation or null | | `legacy_metadata` | Compatibility projection | | `latest_version` | Available reference-output and trajectory context | See `CaseRecord` and `DatasetRecordContent` in the [OpenAPI](https://api.teammately.ai/v1/openapi.yaml) for nested fields. Optional values remain absent or null as returned. ## Frozen evaluation membership `GET /case-manifest` returns `data.manifest` beneath the selected Benchmark Version. Members carry `case_id`, `benchmark_version_case_id`, `dataset_record_version_id` and `content`. Preserve the exact frozen membership when generating results. ## Related reference pages {% related-card-grid title="Related reference pages" %} - [Cases API](/docs/public-api/cases) - [Submit External Evaluation Outputs](/docs/public-api/evaluation-outputs) - [Project Input Schema](/docs/project-settings/input-schema) {% /related-card-grid %} --- id: public-api.case-imports title: Import Cases summary: Submit a multipart Case manifest and referenced files, preserve logical retry identity, and inspect the issued import result. kind: api product_area: public_api status: stable updated: 2026-10-31 canonical: /docs/public-api/case-imports --- # Import Cases ## Definition Current Case imports use multipart submission beneath the organization/project prefix: ```http POST /cases/imports Content-Type: multipart/form-data Idempotency-Key: manifest: metadata.csv or metadata.jsonl files: referenced file parts ``` The actual multipart boundary is generated by the client. This outline is not a JSON-row import contract. ## Manifest and files The manifest identifies Case input/material fields and relative file references. Preserve relative filenames or supported part metadata when attaching referenced bytes. Project Input Schema and the owning importer decide valid content. Do not confuse file paths with artifact authority or rewrite canonical input into legacy metadata just because an older example used it. ## Admission and status Read the returned import identity and status through: ```http GET /cases/imports/{importId} ``` A successful request is not proof that all rows or files completed. Retain original content and the issued import ID until its outcome is known. A repeated POST idempotency key returns `409`; it does not replay the original import receipt. If the response is lost before an ID is known, reconcile the uncertain import with your administrator or support before using another key. The gateway reports unsupported content type, missing configuration and upstream failures through their actual HTTP/code/reason contracts. It does not silently substitute a local importer. ## Submit a manifest Prepare `metadata.csv` or `metadata.jsonl` and its files using the [Project Input Schema](/docs/project-settings/input-schema). Preserve relative filenames in the multipart parts. ### Example: Submit multipart content ```sh curl --fail-with-body \ --header "Authorization: Bearer ${TEAMMATELY_API_KEY}" \ --header "Idempotency-Key: ${IMPORT_OPERATION_KEY}" \ --form 'manifest=@metadata.csv' \ --form 'files=@materials/reference.txt;filename=materials/reference.txt' \ "${PROJECT_URL}/cases/imports" ``` ```python import os from pathlib import Path import uuid from urllib.parse import quote from urllib.request import HTTPRedirectHandler, Request, build_opener class NoRedirect(HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): return None opener = build_opener(NoRedirect()) api_key = os.environ["TEAMMATELY_API_KEY"] url = os.environ["PROJECT_URL"] + "/cases/imports" headers = {"Authorization": "Bearer " + api_key} headers["Idempotency-Key"] = os.environ["IMPORT_OPERATION_KEY"] boundary = "teammately-" + uuid.uuid4().hex body = bytearray() for name, path, filename, content_type in [ ("manifest", Path("metadata.csv"), "metadata.csv", "text/csv"), ("files", Path("materials/reference.txt"), "materials/reference.txt", "text/plain"), ]: body.extend(( f"--{boundary}\r\n" f'Content-Disposition: form-data; name="{name}"; filename="{filename}"\r\n' f"Content-Type: {content_type}\r\n\r\n" ).encode("utf-8")) body.extend(path.read_bytes() + b"\r\n") body.extend(f"--{boundary}--\r\n".encode("utf-8")) headers["Content-Type"] = "multipart/form-data; boundary=" + boundary request = Request(url, data=bytes(body), headers=headers, method="POST") with opener.open(request) as response: print(response.read().decode("utf-8")) ``` ```typescript import { readFile } from "node:fs/promises"; const apiKey = process.env.TEAMMATELY_API_KEY!; { const url = new URL(process.env.PROJECT_URL! + "/cases/imports"); const body = new FormData(); body.append("manifest", new Blob([new Uint8Array(await readFile("metadata.csv"))]), "metadata.csv"); body.append("files", new Blob([new Uint8Array(await readFile("materials/reference.txt"))]), "materials/reference.txt"); const response = await fetch(url, { method: "POST", redirect: "error", headers: { Authorization: `Bearer ${apiKey}`, "Idempotency-Key": process.env.IMPORT_OPERATION_KEY!, }, body, }); if (!response.ok) throw new Error(`HTTP ${response.status}`); console.log(await response.text()); } ``` ```go package main import ( "bytes" "context" "fmt" "io" "log" "mime/multipart" "net/http" "os" ) func main() { if err := run(context.Background()); err != nil { log.Fatal(err) } } func run(ctx context.Context) error { client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } { target := os.Getenv("PROJECT_URL") + "/cases/imports" var body bytes.Buffer writer := multipart.NewWriter(&body) for _, part := range []struct{ field, path, filename string }{ {"manifest", "metadata.csv", "metadata.csv"}, {"files", "materials/reference.txt", "materials/reference.txt"}, } { file, err := os.Open(part.path) if err != nil { return err } field, err := writer.CreateFormFile(part.field, part.filename) if err != nil { file.Close(); return err } _, copyErr := io.Copy(field, file) closeErr := file.Close() if copyErr != nil { return copyErr } if closeErr != nil { return closeErr } } if err := writer.Close(); err != nil { return err } request, err := http.NewRequestWithContext(ctx, "POST", target, &body) if err != nil { return err } request.Header.Set("Authorization", "Bearer " + os.Getenv("TEAMMATELY_API_KEY")) request.Header.Set("Idempotency-Key", os.Getenv("IMPORT_OPERATION_KEY")) request.Header.Set("Content-Type", writer.FormDataContentType()) response, err := client.Do(request) if err != nil { return err } payloadBytes, readErr := io.ReadAll(response.Body) response.Body.Close() if readErr != nil { return readErr } fmt.Println(string(payloadBytes)) if response.StatusCode < 200 || response.StatusCode >= 300 { return fmt.Errorf("HTTP %d", response.StatusCode) } } return nil } ``` Use one application-owned key for this import. Let curl generate the multipart boundary. Omit `files` when there are no attachments. Retain the exact manifest and bytes. `202` returns the import result; inspect the issued import's row/file outcomes through its status route. [Import Case Examples](/docs/integrations/import-case-examples) covers interactive mapping. ## Related reference pages {% related-card-grid title="Related reference pages" %} - [Cases API](/docs/public-api/cases) - [Case Serialization](/docs/public-api/case-schema) - [API Conventions](/docs/public-api/conventions) {% /related-card-grid %} --- id: public-api.evaluation-outputs title: Submit External Evaluation Outputs summary: Create an external Run from actual first results, append missing Case responses, and explicitly complete its Trial for evaluation. kind: api product_area: public_api status: stable updated: 2026-10-31 canonical: /docs/public-api/evaluation-outputs --- # Submit External Evaluation Outputs ## Definition Use the organization/project/Benchmark/Version prefix and retrieve `GET /case-manifest` for exact frozen Cases. Product Runs and Trials retain the API's existing `run-groups` and child `runs` wire identities. ## Create with actual results ```http POST /run-groups Idempotency-Key: ``` ```json { "name": "Application comparison", "results": [ { "case_id": "", "output": "" } ] } ``` The placeholder ID must be replaced with an issued manifest Case ID. This command atomically creates the Group and first child from real results; it does not reserve an empty reference Run. ## Append and complete Use the returned IDs and links. `POST /run-groups/{groupId}/runs` starts another child Trial in that external Group. GET/POST `/run-groups/{groupId}/runs/{runId}/results` reads or appends Case results. Each item contains exactly one output or failure. Optional fields include exact membership/version references, trajectory, latency, usage and cost. Native JSON and whitespace are retained. Accepted Case results cannot be replaced. `POST .../complete` accepts `allow_partial`. Missing Cases require explicit partial completion. Repeated completion returns the recorded outcome; an explicit failed-admission retry does not change accepted content. ## Boundaries Managed and controlled-evaluation Groups reject externally submitted responses. Corrections require a new child Trial. The existing external Group can accept that new child through its registered route; a new Group is also an explicit option. Accepted execution failures remain unavailable response judgments rather than fabricated rubric failures. ## Request sequence Configure `BENCHMARK_URL` with the [quickstart](/docs/public-api/quickstart). Prepare `first-results.json` using the body above, an actual manifest Case ID and its application response. ### Example: Read the manifest and submit first results ```sh curl --fail-with-body \ --header "Authorization: Bearer ${TEAMMATELY_API_KEY}" \ "${BENCHMARK_URL}/case-manifest" curl --fail-with-body \ --header "Authorization: Bearer ${TEAMMATELY_API_KEY}" \ --header 'Content-Type: application/json' \ --header "Idempotency-Key: ${RESULT_OPERATION_KEY}" \ --data-binary @first-results.json \ "${BENCHMARK_URL}/run-groups" ``` ```python import os from pathlib import Path from urllib.parse import quote from urllib.request import HTTPRedirectHandler, Request, build_opener class NoRedirect(HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): return None opener = build_opener(NoRedirect()) api_key = os.environ["TEAMMATELY_API_KEY"] url = os.environ["BENCHMARK_URL"] + "/case-manifest" headers = {"Authorization": "Bearer " + api_key} request = Request(url, headers=headers, method="GET") with opener.open(request) as response: print(response.read().decode("utf-8")) url = os.environ["BENCHMARK_URL"] + "/run-groups" headers = {"Authorization": "Bearer " + api_key} headers["Idempotency-Key"] = os.environ["RESULT_OPERATION_KEY"] headers["Content-Type"] = "application/json" body = Path("first-results.json").read_bytes() request = Request(url, data=body, headers=headers, method="POST") with opener.open(request) as response: print(response.read().decode("utf-8")) ``` ```typescript import { readFile } from "node:fs/promises"; const apiKey = process.env.TEAMMATELY_API_KEY!; { const url = new URL(process.env.BENCHMARK_URL! + "/case-manifest"); const response = await fetch(url, { method: "GET", redirect: "error", headers: { Authorization: `Bearer ${apiKey}`, }, }); if (!response.ok) throw new Error(`HTTP ${response.status}`); console.log(await response.text()); } { const url = new URL(process.env.BENCHMARK_URL! + "/run-groups"); const response = await fetch(url, { method: "POST", redirect: "error", headers: { Authorization: `Bearer ${apiKey}`, "Idempotency-Key": process.env.RESULT_OPERATION_KEY!, "Content-Type": "application/json", }, body: new Uint8Array(await readFile("first-results.json")).buffer, }); if (!response.ok) throw new Error(`HTTP ${response.status}`); console.log(await response.text()); } ``` ```go package main import ( "bytes" "context" "fmt" "io" "log" "net/http" "os" ) func main() { if err := run(context.Background()); err != nil { log.Fatal(err) } } func run(ctx context.Context) error { client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } { target := os.Getenv("BENCHMARK_URL") + "/case-manifest" request, err := http.NewRequestWithContext(ctx, "GET", target, nil) if err != nil { return err } request.Header.Set("Authorization", "Bearer " + os.Getenv("TEAMMATELY_API_KEY")) response, err := client.Do(request) if err != nil { return err } payloadBytes, readErr := io.ReadAll(response.Body) response.Body.Close() if readErr != nil { return readErr } fmt.Println(string(payloadBytes)) if response.StatusCode < 200 || response.StatusCode >= 300 { return fmt.Errorf("HTTP %d", response.StatusCode) } } { target := os.Getenv("BENCHMARK_URL") + "/run-groups" payload, err := os.ReadFile("first-results.json") if err != nil { return err } request, err := http.NewRequestWithContext(ctx, "POST", target, bytes.NewReader(payload)) if err != nil { return err } request.Header.Set("Authorization", "Bearer " + os.Getenv("TEAMMATELY_API_KEY")) request.Header.Set("Idempotency-Key", os.Getenv("RESULT_OPERATION_KEY")) request.Header.Set("Content-Type", "application/json") response, err := client.Do(request) if err != nil { return err } payloadBytes, readErr := io.ReadAll(response.Body) response.Body.Close() if readErr != nil { return readErr } fmt.Println(string(payloadBytes)) if response.StatusCode < 200 || response.StatusCode >= 300 { return fmt.Errorf("HTTP %d", response.StatusCode) } } return nil } ``` Retain Group and child Run IDs. Append additional batches under the child's `results` route with a key per logical batch. Recover a lost response with its original bytes and key. Read import state before completing. Send `{"allow_partial":false}` to `POST /run-groups/{groupId}/runs/{runId}/complete`; use `true` only for intentionally missing Cases. A failed evaluation-admission retry uses `retry:true` and retains the original partial policy. ## Inspect the result Read `GET /runs/{runId}` for judging progress, then [retrieve results](/docs/public-api/benchmark-results). Missing output, execution failure and an unfavorable rubric judgment are different outcomes. For a full local-file walkthrough, see [CLI external outputs](/docs/cli/external-outputs). ## Related reference pages {% related-card-grid title="Related reference pages" %} - [Retrieve Benchmark Results](/docs/public-api/benchmark-results) - [Result Serialization](/docs/public-api/result-schema) - [API Conventions](/docs/public-api/conventions) {% /related-card-grid %}