> ## Documentation Index
> Fetch the complete documentation index at: https://docs.epode.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Submit Agent Outcome Review — POST /api/v2/outcomes

> Submit a compact success, partial, or failure outcome review from a customer agent using a scoped afr2_ capability token. Idempotent and privacy-enforced.

The outcomes endpoint is the single write surface that a customer's autonomous agent calls after it has used a product response. The agent receives everything it needs — the URL, the capability token, and a brief instruction — inside the `_agentFeedback` field that your SDK embeds in the product response. The agent reads the contract, evaluates the result, and POSTs a two-field review. Your backend never needs to make this call directly.

## Endpoint

```
POST /api/v2/outcomes
```

**Base URL:** `https://agent-feedback-api-production.up.railway.app`

## Authorization

Pass the scoped capability token that was embedded in the `_agentFeedback` contract. This is **not** the company product key.

```http theme={null}
Authorization: Bearer afr2_<capability_token>
```

The token is bound to one interaction, expires two hours after the product response was issued, and is verified by the server using the HMAC-SHA256 signature created when the SDK signed it locally.

<Warning>
  Never pass the `af_live_` product key to this endpoint. The product key must remain on your backend. If the server receives a token that does not start with `afr2_`, it returns `401 Unauthorized` immediately.
</Warning>

## Request body

Send a JSON object with exactly two fields. The endpoint rejects unknown fields, recursively nested content (prompts, transcripts, raw tool payloads), and any `note` value that looks like a secret or credential.

<ParamField body="outcome" type="string" required>
  The agent's evaluation of whether the product result satisfied the task.

  Allowed values: `"success"`, `"partial"`, `"failure"`.

  * **`success`** — the result fully satisfied the task.
  * **`partial`** — the result was useful but incomplete or required workarounds.
  * **`failure`** — the result did not satisfy the task.
</ParamField>

<ParamField body="note" type="string" required>
  One short sentence explaining the outcome. Must be between **8 and 500 characters** after whitespace normalization.

  Keep this purely factual and metadata-level. Do not include user prompts, transcripts, PII, credentials, raw product payloads, or any other sensitive content. The server actively rejects secret-shaped strings and will return `400` if it detects them.

  **Good:** `"The search result contained the status information needed to complete the task."`

  **Bad:** `"User asked: 'what is my order status?' and the API returned order #12345 for jane@example.com."`
</ParamField>

## Example request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://agent-feedback-api-production.up.railway.app/api/v2/outcomes \
    -H "Authorization: Bearer afr2_<capability_token>" \
    -H "Content-Type: application/json" \
    -d '{"outcome":"success","note":"The search result contained the status information needed to complete the task."}'
  ```

  ```json Request body theme={null}
  {
    "outcome": "success",
    "note": "The search result contained the status information needed to complete the task."
  }
  ```
</CodeGroup>

## Response

A successful submission returns HTTP `200` with a JSON body regardless of whether the review was accepted for the first time or is a duplicate.

<ResponseField name="accepted" type="boolean">
  Always `true` for a valid submission. The endpoint is idempotent: duplicate requests with the same capability token return the original review rather than creating a new record.
</ResponseField>

<ResponseField name="interactionId" type="string">
  The UUID interaction ID extracted from the capability token claims. Use this to correlate the review with your telemetry records.
</ResponseField>

<ResponseField name="review" type="object">
  The stored review record.

  <Expandable title="review fields">
    <ResponseField name="review.id" type="string">
      UUID identifier for this specific review record.
    </ResponseField>

    <ResponseField name="review.interactionId" type="string">
      UUID of the interaction this review is attached to.
    </ResponseField>

    <ResponseField name="review.outcome" type="string">
      The outcome value as stored: `"success"`, `"partial"`, or `"failure"`.
    </ResponseField>

    <ResponseField name="review.note" type="string">
      The whitespace-normalized note as stored.
    </ResponseField>

    <ResponseField name="review.source" type="string">
      How the review was submitted. Always `"capability"` for reviews submitted via the `afr2_` token path.
    </ResponseField>

    <ResponseField name="review.createdAt" type="string">
      ISO 8601 timestamp for when the review was first recorded.
    </ResponseField>
  </Expandable>
</ResponseField>

### Successful submission (HTTP 200)

```json theme={null}
{
  "accepted": true,
  "interactionId": "b3e2f1a0-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
  "review": {
    "id": "d7f8e9a0-1b2c-3d4e-5f6a-7b8c9d0e1f2a",
    "interactionId": "b3e2f1a0-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
    "outcome": "success",
    "note": "The search result contained the status information needed to complete the task.",
    "source": "capability",
    "createdAt": "2026-07-28T04:00:00.000Z"
  }
}
```

### Duplicate submission (HTTP 200)

When the same capability token is used a second time, the server returns the original review unchanged. No data is overwritten.

```json theme={null}
{
  "accepted": true,
  "interactionId": "b3e2f1a0-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
  "review": {
    "id": "d7f8e9a0-1b2c-3d4e-5f6a-7b8c9d0e1f2a",
    "interactionId": "b3e2f1a0-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
    "outcome": "success",
    "note": "The search result contained the status information needed to complete the task.",
    "source": "capability",
    "createdAt": "2026-07-28T04:00:00.000Z"
  }
}
```

## Idempotency

Submitting a review is idempotent at the capability token level. The first accepted review wins. All subsequent requests using the same capability token return the original review with HTTP `200`. This means you can safely retry a `5xx` response once without creating duplicate records.

## Error responses

| Status                     | Cause                                                                                                                                                                                     |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`          | Malformed JSON, unknown fields in the request body, nested content that the privacy filter rejects, or a `note` that matches a secret pattern.                                            |
| `401 Unauthorized`         | The `Authorization` header is missing or does not start with `afr2_`, the capability signature is invalid, the token has expired (>2 hours), or the signing product key has been revoked. |
| `422 Unprocessable Entity` | `outcome` is not one of `"success"`, `"partial"`, `"failure"`, or `note` is fewer than 8 characters or exceeds 500 characters.                                                            |

### Example 401 response

```json theme={null}
{
  "error": "Unauthorized"
}
```

### Example 422 response

```json theme={null}
{
  "error": "outcome must be one of: success, partial, failure"
}
```
