---
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: <logical-command-key>
```

```json
{
  "name": "Application comparison",
  "results": [
    {
      "case_id": "<issued-case-id>",
      "output": "<response produced by the application>"
    }
  ]
}
```

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 %}
