---
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: <logical-command-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 %}
