# Public API Generated: 2026-11-02T05:41:25.416Z Source build: local Canonical docs: https://teammately.ai/docs --- id: public-api.overview title: Public API summary: Connect applications to Cases, Fieldloop ingestion, benchmark execution and evaluation results through the organization-scoped API. kind: api product_area: public_api status: stable updated: 2026-10-31 canonical: /docs/public-api --- # Public API ## Definition The customer gateway uses the base `https://api.teammately.ai/v1` and organization-scoped resources beneath `/organizations/{organizationKey}/projects/{projectId}`. Start with [Your First API Request](/docs/public-api/quickstart). Use REST for direct request control, [SDKs](/docs/sdks) for application ingestion, or the [CLI](/docs/cli) for coding-agent and local-file workflows. ## Contract families The [endpoint catalog](/docs/public-api/endpoints) includes Projects, dimensions/terms and dataset operations as well as the workflow families below. | Family | Operations and guide | | --- | --- | | [Cases](/docs/public-api/cases) | List/detail and single/batch reference responses | | [Case imports](/docs/public-api/case-imports) | Multipart import and status | | [Fieldloop](/docs/public-api/fieldloop) | Batch submission and receipts | | [External outputs](/docs/public-api/evaluation-outputs) | Frozen manifest, real first results, batches and completion | | [Managed execution](/docs/public-api/managed-execution) | Source submission, runtime preparation and admission | | [Results](/docs/public-api/benchmark-results) | Pinned context, individual Trial results and comparisons | ## OpenAPI and conventions The [OpenAPI YAML](https://api.teammately.ai/v1/openapi.yaml) and [OpenAPI JSON](https://api.teammately.ai/v1/openapi.json) describe request fields, response schemas and per-operation authentication. These guides explain operation sequences and asynchronous outcomes. Read [Authentication](/docs/public-api/authentication) and [Conventions](/docs/public-api/conventions) before writing. Envelopes and pagination vary by resource. Supply credentials through the host's secret mechanism and use identifiers from Teammately. Use the request panel's language selector to switch between equivalent cURL, Python, TypeScript and Go examples. Copy copies the selected language. JSON request bodies and HTTP signatures are shown separately. These are direct HTTP examples, not SDK method declarations. Python uses its standard library; TypeScript uses Node.js 22+ built-in fetch and file APIs; Go uses its standard library. [Application SDKs](/docs/sdks) describe the resources each Teammately library exposes. The [quickstart](/docs/public-api/quickstart) exports the nonsecret `PROJECT_URL` and `BENCHMARK_URL` values. Export each example's additional identifiers and logical-command keys before running it. Supply `TEAMMATELY_API_KEY` through your approved secret environment. Keep original body files and request identity when following the operation's recovery rules. ## Runs, Trials and wire names The product calls an external cohort a **Run** and child executions **Trials**. API paths retain `run-groups` for the cohort and `runs` for an individual child. Keep both returned IDs. Assessment and comparison reads use the child `runId`, not the group ID. ## Related reference pages {% related-card-grid title="Related reference pages" %} - [API Authentication](/docs/public-api/authentication) - [API Conventions](/docs/public-api/conventions) - [Cases API](/docs/public-api/cases) - [Import Cases](/docs/public-api/case-imports) - [Submit External Evaluation Outputs](/docs/public-api/evaluation-outputs) - [Retrieve Benchmark Results](/docs/public-api/benchmark-results) - [Result Serialization](/docs/public-api/result-schema) {% /related-card-grid %} --- id: public-api.authentication title: API Authentication summary: Distinguish API-key capability, delegated External Agents login, current destination access, and browser authentication. kind: api product_area: public_api status: stable updated: 2026-10-31 canonical: /docs/public-api/authentication --- # API Authentication ## Definition The gateway accepts API keys and delegated native-login access tokens on their supported routes. Browser session cookies remain the interactive app path; these credential types are not interchangeable. ## API keys Keys belong to a workspace. Base capability can be read-only, full access or Fieldloop submission. Project restrictions, allowed actions and expiry narrow that capability when supplied. A new Fieldloop submission key carries that workspace capability. The caller still supplies the Project and source destination, and current access is checked at intake. Previously issued restricted keys retain their restrictions. Changing access requires issuing the intended credential; renaming a key does not broaden it. Never place values in URLs, source, logs or copied agent tasks. ## Delegated login Native login is scoped to supported External Agents benchmark routes and organization login status. Delegation uses `external-agents:read` and `external-agents:write`, plus current project read/manage authorization. It does not provision a user, select a fallback organization or grant every endpoint. The native client uses authorization-code login with PKCE and an OS credential store. Auth discovery/session routes are distinct from customer operations. ## Request headers Supply the credential as a bearer token. `TEAMMATELY_API_KEY` is injected by your secret mechanism; configure `PROJECT_URL` with the [quickstart](/docs/public-api/quickstart). ### Example: Read a Case page ```sh curl --fail-with-body \ --header "Authorization: Bearer ${TEAMMATELY_API_KEY}" \ "${PROJECT_URL}/cases?limit=1&offset=0" ``` ```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": "1", "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")) ``` ```typescript const apiKey = process.env.TEAMMATELY_API_KEY!; { const url = new URL(process.env.PROJECT_URL! + "/cases"); url.searchParams.set("limit", "1"); 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()); } ``` ```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", "1") 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) } } return nil } ``` ## Choose the credential | Operation | Credential boundary | | --- | --- | | Case reads | API key with Project read access | | Case imports/reference writes | API key with the required write access | | Fieldloop submission/receipts | Fieldloop capability, destination permission and original-submitter receipt ownership | | External Agents reads | Authorized API key, or delegated `external-agents:read` plus Project read access | | External Agents writes | Authorized API key, or delegated `external-agents:write` plus Project management access | For delegated terminal access, follow [CLI Authentication](/docs/cli/authentication). Application ingestion uses an API key without browser login. A read-only MCP tool does not narrow the credential's permission elsewhere. ## Access failures For `401`, check the credential's presence, validity and API origin. For `403`, check the workspace, Project, capability, restrictions and delegated scope. Organization IP restrictions can apply. Correct access through normal administration. ## Related reference pages {% related-card-grid title="Related reference pages" %} - [Public API](/docs/public-api) - [API Conventions](/docs/public-api/conventions) - [API Keys and Delegated Access](/docs/admin-console/api-keys) {% /related-card-grid %} --- id: public-api.benchmark-results title: Retrieve Benchmark Results summary: Read scoped individual Run context, complete rubric results, instructions, and comparable Run differences. kind: api product_area: public_api status: stable updated: 2026-10-31 canonical: /docs/public-api/benchmark-results --- # Retrieve Benchmark Results ## Definition Under the organization/project/Benchmark/Version prefix, current gateway routes include: ```http GET /version-context GET /runs/{runId} GET /runs/{runId}/evaluation-context GET /runs/{runId}/evaluation-results GET /runs/{runId}/evaluation-instructions GET /comparison/{runId}/summary GET /comparison/{runId}/cases ``` External Group/child reads additionally use their returned nested links. ## Read the exact population Verify that the individual Run belongs to the selected Benchmark Version. Read available Case outputs, rubric judgments, native interpretation, rationale and incomplete state. Page through the authoritative filtered result population rather than treating the first page as all results. Instructions and context orient the coding agent; neither is a mutation or a completed evaluation. ## Compare Comparisons preserve individual Run grain and recorded conditions. Missing outputs, profile drift or unavailable native results can prevent a definitive comparison. A later independent Run does not silently replace the reference chosen before outcomes were known. Record the exact starting and candidate Run IDs. Do not interpret legacy compatibility columns as mandatory binary rules when the current Regime supplies native point/gate meaning. ## Read a bounded page ### Example: Read evaluation results ```sh curl --fail-with-body \ --header "Authorization: Bearer ${TEAMMATELY_API_KEY}" \ "${BENCHMARK_URL}/runs/${RUN_ID}/evaluation-results?limit=20&offset=0" ``` ```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["BENCHMARK_URL"] + "/runs/" + quote(os.environ["RUN_ID"], safe="") + "/evaluation-results" url += "?" + urlencode({"limit": "20", "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")) ``` ```typescript const apiKey = process.env.TEAMMATELY_API_KEY!; { const url = new URL(process.env.BENCHMARK_URL! + "/runs/" + encodeURIComponent(process.env.RUN_ID!) + "/evaluation-results"); url.searchParams.set("limit", "20"); 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()); } ``` ```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("BENCHMARK_URL") + "/runs/" + url.PathEscape(os.Getenv("RUN_ID")) + "/evaluation-results" query := url.Values{} query.Set("limit", "20") 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) } } return nil } ``` Use the child Trial's `runId`. The `data` payload holds the page. `limit` accepts 1–100 (default 20); `offset` is nonnegative. Page through the returned population instead of treating one page as all results. | Query | Selection | | --- | --- | | `result` | `passed`, `failed` or `missing` rubric observations | | `case_id`, `policy_id` | One Case or Policy | | `case_rubric_pair_id` | One Case/Rubric pair | | `dimension_id`, `ontology_id` | Declared coverage population | The same filters apply to `evaluation-context`. Unknown/repeated parameters are rejected. Run and instruction reads take no query parameters. ## Compare two Trials ### Example: Compare two Trials ```sh curl --fail-with-body --get \ --header "Authorization: Bearer ${TEAMMATELY_API_KEY}" \ --data-urlencode "target_run_id=${TARGET_RUN_ID}" \ "${BENCHMARK_URL}/comparison/${BASELINE_RUN_ID}/summary" ``` ```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["BENCHMARK_URL"] + "/comparison/" + quote(os.environ["BASELINE_RUN_ID"], safe="") + "/summary" url += "?" + urlencode({"target_run_id": os.environ["TARGET_RUN_ID"]}) 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.BENCHMARK_URL! + "/comparison/" + encodeURIComponent(process.env.BASELINE_RUN_ID!) + "/summary"); url.searchParams.set("target_run_id", process.env.TARGET_RUN_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("BENCHMARK_URL") + "/comparison/" + url.PathEscape(os.Getenv("BASELINE_RUN_ID")) + "/summary" query := url.Values{} query.Set("target_run_id", os.Getenv("TARGET_RUN_ID")) 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) } } return nil } ``` Use `/cases` for paged changes, with `limit` and `offset`. Both child IDs must belong to the scope. Inspect comparability and incompleteness before interpreting a delta. ## Related reference pages {% related-card-grid title="Related reference pages" %} - [Result Serialization](/docs/public-api/result-schema) - [Submit External Evaluation Outputs](/docs/public-api/evaluation-outputs) - [Evaluation Results](/docs/benchmark-evaluations/results) {% /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.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.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.conventions title: API Conventions summary: Use organization-scoped paths, operation-specific envelopes, issued IDs, exact retry identity, and explicit completion. kind: api product_area: public_api status: stable updated: 2026-10-31 canonical: /docs/public-api/conventions --- # API Conventions ## Definition The resource prefix is `/v1/organizations/{organizationKey}/projects/{projectId}`. Benchmark operations continue beneath the selected Benchmark and immutable Version. Paths and IDs are scoped; a readable title is not a join key. ## Envelopes and pagination Case lists return `message` and `data: {columns, rows}`; detail returns `message` and `result`. Fieldloop and benchmark operations use their documented `data` payloads. Parse the called operation's envelope. Case reads use limit/offset pagination, defaulting to 10 rows and accepting a positive limit through 1,000. Other readers have their own pagination. Preserve returned continuation information instead of assuming a cursor shape universally. Case reads emit ETag and support If-None-Match. Concurrency and mutation preconditions belong to the particular operation. ## Writes and retries Use an Idempotency-Key where the operation requires it. Preserve exact request content and logical identity, but follow that operation's recovery contract. Changed content is a different command. External results append only to the issued Trial. Completion freezes accepted responses and admits evaluation separately. A failed admission retry does not replace those responses. ## IDs and errors Use database/API-issued opaque IDs. Example placeholders are not usable IDs. Keep the original HTTP status, structured code and actionable reason; no global error envelope is promised across legacy and forwarded families. ## HTTP outcomes | Outcome | Next step | | --- | --- | | `200` or `201` | Inspect the resource or per-item outcomes | | `202` | Retain the issued identity and inspect processing state | | `304` | Use the representation associated with the matching ETag | | `400` or `422` | Correct the request using its code and reason | | `401` or `403` | Correct credentials or scope | | `409` or `412` | Inspect immutable state, idempotency or preconditions | | `429` | Honor `Retry-After` when supplied | | `502`, `503` or `504` | Preserve command identity before recovering an uncertain write | Not every endpoint emits every status. The [OpenAPI](https://api.teammately.ai/v1/openapi.yaml) lists operation-specific responses; one rate-limit response does not define a universal quota. ## Recover without duplicating work Fieldloop and benchmark submission workflows support their documented exact-content recovery. Preserve original bytes/key and inspect the returned receipt. A client timeout does not undo admitted work. Case imports and batch Case reference writes use duplicate-key rejection: repeating a key returns `409`, not a replay of the original response. Retain the issued import identity when available and read its status. If a response is lost before its identity is known, reconcile through your administrator or support before submitting with a new key; a fresh key can create another operation. Do not assume duplicate rejection proves whether the first handler completed. See [SDK Delivery and Recovery](/docs/sdks/delivery-and-recovery) for the Fieldloop client path. ## Related reference pages {% related-card-grid title="Related reference pages" %} - [Public API](/docs/public-api) - [API Authentication](/docs/public-api/authentication) - [Submit External Evaluation Outputs](/docs/public-api/evaluation-outputs) {% /related-card-grid %} --- id: public-api.endpoints title: API Endpoint Reference summary: Locate canonical organization-scoped operations and their request/response schemas. kind: api product_area: public_api status: stable updated: 2026-10-31 canonical: /docs/public-api/endpoints --- # API Endpoint Reference ## Definition Use this catalog to locate an operation. The [OpenAPI YAML](https://api.teammately.ai/v1/openapi.yaml) and [JSON](https://api.teammately.ai/v1/openapi.json) supply its full body, query, response and security schemas. [Authentication](/docs/public-api/authentication) is operation-specific; native CLI delegation does not authorize every resource below. ## Projects These paths follow `/v1/organizations/{organizationKey}`: | Method | Path | Operation | | --- | --- | --- | | `GET` | `/projects` | List Projects | | `POST` | `/projects` | Create a Project | | `GET` | `/projects/{projectId}` | Read a Project | | `PATCH` | `/projects/{projectId}` | Update a Project | ## Dimensions and terms These and the following project operations follow `/v1/organizations/{organizationKey}/projects/{projectId}`. Keep the API's `dimensions`/`terms` wire names when working with the corresponding product coverage concepts. | Method | Path | Operation | | --- | --- | --- | | `GET`, `POST` | `/dimensions` | List/create dimensions | | `GET`, `PATCH` | `/dimensions/{dimensionId}` | Read/update a dimension | | `GET`, `POST` | `/dimensions/{dimensionId}/terms` | List/create terms | | `GET`, `PATCH` | `/dimensions/{dimensionId}/terms/{termId}` | Read/update a term | ## Dataset records and views | Method | Path | Operation | | --- | --- | --- | | `GET` | `/datasets/views` | List views | | `GET` | `/datasets/views/{viewId}` | Read a view | | `GET` | `/datasets/views/{viewId}/records` | Read view records | | `GET` | `/datasets/views/{viewId}/representation` | Read representation | | `GET` | `/datasets/records` | List records | | `GET` | `/datasets/records/{recordId}` | Read a record | | `GET` | `/datasets/records/{recordId}/versions` | List record versions | | `POST` | `/datasets/upload` | Upload records using its operation schema | ## Cases and Fieldloop | Method | Path | Guide | | --- | --- | --- | | `GET` | `/cases`, `/cases/{caseId}` | [Case reads](/docs/public-api/cases) | | `PUT` | `/cases/{caseId}/reference-result` | [Reference responses](/docs/public-api/cases#write-a-reference-response) | | `POST` | `/cases/reference-results` | Batch reference responses | | `POST` | `/cases/imports` | [Multipart imports](/docs/public-api/case-imports) | | `GET` | `/cases/imports/{importId}` | Import status | | `POST` | `/fieldloop/sources/{sourceId}/batches` | [Fieldloop submission](/docs/public-api/fieldloop) | | `GET` | `/fieldloop/sources/{sourceId}/batches/{batchId}` | Fieldloop receipt | Case imports and batch reference writes reject repeated idempotency keys rather than replaying receipts. Use their [specific recovery rules](/docs/public-api/conventions#recover-without-duplicating-work). ## Benchmark context and external outputs The following paths continue beneath `/benchmarks/{benchmarkId}/versions/{benchmarkVersionId}`. `run-groups` identifies the product Run; a nested child `runId` identifies a Trial. | Method | Path | Operation | | --- | --- | --- | | `GET` | `/version-context` | Pinned summary/Cases/Policies/Rubrics | | `GET` | `/case-manifest` | Frozen execution population | | `GET` | `/run-groups` | Existing groups and submission context | | `POST` | `/run-groups` | Create a group/first child with actual results | | `POST` | `/run-groups/{groupId}/runs` | Create an additional Trial | | `GET` | `/run-groups/{groupId}/runs/{runId}` | Import state | | `GET`, `POST` | `/run-groups/{groupId}/runs/{runId}/results` | Read/append submitted outputs | | `POST` | `/run-groups/{groupId}/runs/{runId}/complete` | Freeze outputs and request evaluation | Follow [External Evaluation Outputs](/docs/public-api/evaluation-outputs) for the complete lifecycle and immutable-response rules. ## Managed execution and assessment | Method | Path | Operation | | --- | --- | --- | | `GET` | `/managed-run-profile` | Current execution policy/token | | `POST` | `/harness-submissions` | Save exact source | | `POST` | `/harness-runtime-preparations` | Request runtime preparation | | `POST` | `/managed-runs` | Request managed execution | | `GET` | `/runs/{runId}` | Individual execution/progress | | `GET` | `/runs/{runId}/evaluation-context` | Bounded context and observations | | `GET` | `/runs/{runId}/evaluation-results` | Paged judgments | | `GET` | `/runs/{runId}/evaluation-instructions` | Evaluation procedure | | `GET` | `/comparison/{runId}/summary` | Explicit baseline/target summary | | `GET` | `/comparison/{runId}/cases` | Paged Case changes | See [Managed Execution](/docs/public-api/managed-execution) and [Results](/docs/public-api/benchmark-results) for sequence, filters and interpretation. --- 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 %} --- id: public-api.fieldloop title: Fieldloop Batches and Receipts summary: Submit captured logs, preserve retry identity and inspect admission and row processing separately. kind: api product_area: public_api status: stable updated: 2026-10-31 canonical: /docs/public-api/fieldloop --- # Fieldloop Batches and Receipts ## Definition Submit captured application records to an existing source with a Fieldloop Submission key from **Admin → API Keys**. New keys carry that capability across the workspace. Configure the intended Project/source; older key restrictions and current permissions remain enforced. Receipt reads belong to the original submitter. ## Submit a batch ```http POST /v1/organizations/{organizationKey}/projects/{projectId}/fieldloop/sources/{sourceId}/batches Authorization: Bearer Content-Type: application/json Idempotency-Key: ``` ### Example: Captured log body ```json { "logs": [{ "source_event_id": "support-ticket-1042:reply", "occurred_at": "2026-10-31T09:30:00Z", "input": {"question": "Has my bicycle shipped?"}, "output": {"reply": "Your bicycle is being packed."}, "context": {"application": "Bicycle Shop"} }] } ``` Replace the fictional record with selected, redacted data in `batch.json`. Your application owns `source_event_id`. Each log needs RFC3339 `occurred_at` and at least one of `input`, `output`, `messages`, `trajectory` or `action`. Optional feedback is nested in its log; source relationships use `provider_ref`, `session_ref` and `trace_ref`. ### Example: Submit a batch ```sh SOURCE_URL="${PROJECT_URL}/fieldloop/sources/${TEAMMATELY_FIELDLOOP_SOURCE}" curl --fail-with-body \ --header "Authorization: Bearer ${TEAMMATELY_API_KEY}" \ --header 'Content-Type: application/json' \ --header "Idempotency-Key: ${BATCH_OPERATION_KEY}" \ --data-binary @batch.json \ "${SOURCE_URL}/batches" ``` ```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["PROJECT_URL"] + "/fieldloop/sources/" + quote(os.environ["TEAMMATELY_FIELDLOOP_SOURCE"], safe="") + "/batches" headers = {"Authorization": "Bearer " + api_key} headers["Idempotency-Key"] = os.environ["BATCH_OPERATION_KEY"] headers["Content-Type"] = "application/json" body = Path("batch.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.PROJECT_URL! + "/fieldloop/sources/" + encodeURIComponent(process.env.TEAMMATELY_FIELDLOOP_SOURCE!) + "/batches"); const response = await fetch(url, { method: "POST", redirect: "error", headers: { Authorization: `Bearer ${apiKey}`, "Idempotency-Key": process.env.BATCH_OPERATION_KEY!, "Content-Type": "application/json", }, body: new Uint8Array(await readFile("batch.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" "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") + "/fieldloop/sources/" + url.PathEscape(os.Getenv("TEAMMATELY_FIELDLOOP_SOURCE")) + "/batches" payload, err := os.ReadFile("batch.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("BATCH_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 } ``` Use the Project URL from the [quickstart](/docs/public-api/quickstart), an existing issued source ID and one logical batch key. Preserve the exact file/key for uncertain retries. ## Read the receipt `202` acknowledges durable receipt of the bytes. Retain `data.batch_id`: ### Example: Read a receipt ```sh curl --fail-with-body \ --header "Authorization: Bearer ${TEAMMATELY_API_KEY}" \ "${SOURCE_URL}/batches/${BATCH_ID}" ``` ```python import os 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"] + "/fieldloop/sources/" + quote(os.environ["TEAMMATELY_FIELDLOOP_SOURCE"], safe="") + "/batches/" + quote(os.environ["BATCH_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! + "/fieldloop/sources/" + encodeURIComponent(process.env.TEAMMATELY_FIELDLOOP_SOURCE!) + "/batches/" + encodeURIComponent(process.env.BATCH_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") + "/fieldloop/sources/" + url.PathEscape(os.Getenv("TEAMMATELY_FIELDLOOP_SOURCE")) + "/batches/" + url.PathEscape(os.Getenv("BATCH_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 } ``` | Field | Interpretation | | --- | --- | | `batch_id`, `source_id`, `sequence` | Issued identity and destination order | | `status` | `uploading`, `received`, `processing`, `processed`, `failed`, `cancelled` or `deleted` | | `row_count` | Submitted population | | `accepted_count`, `duplicate_count` | Accepted records and recognized duplicates | | `conflict_count`, `rejected_count` | Rows requiring inspection | | `rows`, `safe_error_code` | Per-row outcomes and error code when returned | `processed` can include rejected/conflicting rows. Polling reads an existing batch rather than submitting another one. ## Recovery and inspection Replay a lost response with identical bytes/key. Retain source records until admission is known. Inspect **Fieldloop → Logs** for original content and processing state. Source consent, Threads and Case creation are separate workflows. [SDK guides](/docs/sdks) cover continuous ingestion. `FieldloopBatch`, `FieldloopLog` and `FieldloopReceiptEnvelope` in the [OpenAPI](https://api.teammately.ai/v1/openapi.yaml) define request/receipt fields. ## Related reference pages {% related-card-grid %} - [API keys](/docs/admin-console/api-keys) - [Fieldloop troubleshooting](/docs/troubleshooting/fieldloop) {% /related-card-grid %} --- id: public-api.managed-execution title: Managed Harness Execution API summary: Submit Harness source, prepare its runtime and request execution as distinct recoverable operations. kind: api product_area: public_api status: stable updated: 2026-10-31 canonical: /docs/public-api/managed-execution --- # Managed Harness Execution API ## Definition Managed execution runs an explicitly submitted Harness against the selected Benchmark Version. It is an alternative to [submitting application outputs](/docs/public-api/evaluation-outputs). Paths below are relative to `BENCHMARK_URL` from the [quickstart](/docs/public-api/quickstart). ## Operation sequence | Method and path | Input | Retain | | --- | --- | --- | | `GET /managed-run-profile` | Selected scope | Execution policy and concurrency token | | `POST /harness-submissions` | `name`, exact `source` | Issued Harness/Version IDs | | `POST /harness-runtime-preparations` | `harness_id`, `harness_version_id` | Preparation acknowledgement/state | | `POST /managed-runs` | Saved IDs and `expected_managed_run_profile_revision` | Every admitted execution | Writes require the operation's `Idempotency-Key`. Preserve exact bytes and a separate key for each command. ## Source package `source.schema_version` is `harness_working_copy.v1`. File entries have relative `path` and UTF-8 `content_text`. The runtime accepts Python `3.12` and the callable `main.py:generate_result(request)`. Source policy permits at most 64 files, 256 KiB each and 1 MiB total. Symlinks, traversal and credential/cache paths are rejected. Use the [CLI managed-Harness guide](/docs/cli/managed-harness) to package local files and retain recovery receipts. The [OpenAPI](https://api.teammately.ai/v1/openapi.yaml) defines all source/runtime fields. ## Prepare and admit Preparation uses the exact saved IDs; acknowledgement does not certify runtime readiness. Inspect returned state and failures. Admission takes those IDs and the current `expected_managed_run_profile_revision`, plus optional existing `compute_profile_id`. The profile token is a concurrency precondition supplied by the server, not a number to choose. Managed admission can return multiple individual executions according to policy. Inspect every returned ID through the [results API](/docs/public-api/benchmark-results). Admission is not completed generation/judging, and it does not activate a Harness. Prepare `managed-run.json` with the saved `harness_id` and exact `harness_version_id`. Read `GET /managed-run-profile` and copy its integer `data.revision` into the request field `expected_managed_run_profile_revision`. Export `BENCHMARK_URL` and `MANAGED_OPERATION_KEY`; the latter identifies this logical admission command. These examples submit the file bytes once, without adding automatic retries. ### Example: Request managed execution ```sh curl --fail-with-body \ --header "Authorization: Bearer ${TEAMMATELY_API_KEY}" \ --header 'Content-Type: application/json' \ --header "Idempotency-Key: ${MANAGED_OPERATION_KEY}" \ --data-binary @managed-run.json \ "${BENCHMARK_URL}/managed-runs" ``` ```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"] + "/managed-runs" headers = {"Authorization": "Bearer " + api_key} headers["Idempotency-Key"] = os.environ["MANAGED_OPERATION_KEY"] headers["Content-Type"] = "application/json" body = Path("managed-run.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! + "/managed-runs"); const response = await fetch(url, { method: "POST", redirect: "error", headers: { Authorization: `Bearer ${apiKey}`, "Idempotency-Key": process.env.MANAGED_OPERATION_KEY!, "Content-Type": "application/json", }, body: new Uint8Array(await readFile("managed-run.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") + "/managed-runs" payload, err := os.ReadFile("managed-run.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("MANAGED_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 } ``` ## Related reference pages {% related-card-grid %} - [Authentication](/docs/public-api/authentication) - [CLI managed Harness](/docs/cli/managed-harness) - [Harnesses](/docs/assets/harnesses) {% /related-card-grid %} --- id: public-api.quickstart title: Your First API Request summary: Configure issued workspace identifiers, authenticate a read and choose an ingestion or evaluation workflow. kind: quickstart product_area: public_api status: stable updated: 2026-10-31 canonical: /docs/public-api/quickstart --- # Your First API Request ## Prerequisites Use an existing workspace and Project, a key with Project read access injected as `TEAMMATELY_API_KEY`, and curl. Copy the organization key and issued Project ID from Teammately; replace the placeholders below. ## Configure the destination ```sh export TEAMMATELY_ORGANIZATION='' export TEAMMATELY_PROJECT='' PROJECT_URL="https://api.teammately.ai/v1/organizations/${TEAMMATELY_ORGANIZATION}" PROJECT_URL="${PROJECT_URL}/projects/${TEAMMATELY_PROJECT}" export PROJECT_URL ``` Only nonsecret identifiers are configured here. Keep the API key out of URLs, source and shell history. ## Send a read request ### Example: Retrieve a Case page ```sh curl --fail-with-body \ --header "Authorization: Bearer ${TEAMMATELY_API_KEY}" \ "${PROJECT_URL}/cases?limit=1&offset=0" ``` ```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": "1", "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")) ``` ```typescript const apiKey = process.env.TEAMMATELY_API_KEY!; { const url = new URL(process.env.PROJECT_URL! + "/cases"); url.searchParams.set("limit", "1"); 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()); } ``` ```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", "1") 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) } } return nil } ``` The response has `message` and `data.columns`/`data.rows`. Empty `rows` can be a valid empty Project. Read a returned `case_id` through `/cases/{caseId}`; that detail response uses `result`. ## Choose the next workflow | Goal | Guide | | --- | --- | | Capture application behavior | [Fieldloop](/docs/public-api/fieldloop) or a [language SDK](/docs/sdks) | | Import Cases | [Case imports](/docs/public-api/case-imports) | | Evaluate application outputs | [External outputs](/docs/public-api/evaluation-outputs) | | Execute a managed Harness | [Managed execution](/docs/public-api/managed-execution) | Benchmark workflows also need the exact Benchmark and immutable Version: ```sh export TEAMMATELY_BENCHMARK='' export TEAMMATELY_BENCHMARK_VERSION='' BENCHMARK_URL="${PROJECT_URL}/benchmarks/${TEAMMATELY_BENCHMARK}" BENCHMARK_URL="${BENCHMARK_URL}/versions/${TEAMMATELY_BENCHMARK_VERSION}" export BENCHMARK_URL ``` ## Success and common failures This read changes no Case. Confirm the intended Project and parse its envelope. A Fieldloop-only key does not authorize a general Case read. For `401`, inspect credential validity; for `403`, inspect capability and restrictions; for `404`, check resource scope. ## Related reference pages {% related-card-grid %} - [Authentication](/docs/public-api/authentication) - [Conventions](/docs/public-api/conventions) - [Permissions troubleshooting](/docs/troubleshooting/permissions) {% /related-card-grid %} --- id: public-api.result-schema title: Result Serialization summary: Interpret actual scoped responses, criteria, native judgments, point scores, completeness, and nullable telemetry without a fabricated global envelope. kind: api product_area: public_api status: stable updated: 2026-10-31 canonical: /docs/public-api/result-schema --- # Result Serialization ## Definition Evaluation access preserves selected public Run fields, Case content and exact references, actual outputs and rationales, frozen criteria/treatment context, native interpretation, coverage/completeness and captured telemetry. The current access contract is `evaluation_access.v1`. Its owning service defines per-endpoint envelopes and pagination. Do not serialize every response as an earlier proposed universal Run/Policy/Rubric object. ## Judgments and scores Preserve binary judgment, unavailable interpretation and the configured point/gate calculation separately. Current scores use exact fractions and can be negative. A completed mandatory-gate failure contributes zero; missing judgments remain incomplete and keep the full planned population unrankable. Manual corrections retain original AI judgment and attributable change metadata. They do not rewrite the criterion or submitted response. ## Outputs and telemetry External response items contain exactly one native JSON output or execution failure. Accepted text whitespace and JSON null remain meaningful submitted values. Optional trajectories describe captured model/tool/agent steps, not hidden reasoning. Latency, usage and cost are nullable captured facts. An absent field does not mean zero. Preserve units and currency identity rather than inventing a normalized total. ## Evaluation page fields The `data` payload contains: | Field | Interpretation | | --- | --- | | `schema_version` | `evaluation_access.v1` | | `run` | Scoped execution and pinned identity | | `rows` | Current page of observations | | `total`, `limit`, `offset` | Filtered population and current page | | `grain` | `case_rubric_observation` when supplied | | `selection`, `sampling` | Selection context when supplied | | `links`, `next_page` | Navigation when supplied | `evaluation-context` also carries procedure/context information. See `BenchmarkEvaluationPage`, `BenchmarkEvaluationContext` and `BenchmarkComparison` in the [OpenAPI](https://api.teammately.ai/v1/openapi.yaml). Native scores, completeness and HTTP success have separate meanings. ## Related reference pages {% related-card-grid title="Related reference pages" %} - [Retrieve Benchmark Results](/docs/public-api/benchmark-results) - [API Conventions](/docs/public-api/conventions) - [Rubric Points and Benchmark Scores](/docs/correctness-governance/scoring) {% /related-card-grid %}