# Import Cases
Generated: 2026-11-03T06:49:33.614Z
Source build: local
Canonical docs: https://teammately.ai/docs
---
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.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.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 %}