# Judge Copy

> Score a draft email for inbox placement. Free.

<Note>
  **Beta.** This endpoint is not in production yet and may change before it ships.
</Note>

Runs the same Copy check as the editor panel: a 0-100 score, the likely placement, flagged sentences and flagged words. Nothing is saved and no AI credit is charged.

## Body Parameters

<ParamField body="subject" type="string">
  Subject line, up to 500 characters.
</ParamField>

<ParamField body="body" type="string">
  Email body, plain text or HTML. At least one of `subject` or `body` is required.
</ParamField>

<ParamField body="sendAsHtml" type="boolean" default="false">
  Set to `true` when the body is HTML.
</ParamField>

<ParamField body="stepNumber" type="integer" default="1">
  Sequence step the copy belongs to (1-50). Step 1 is judged as a first-touch email.
</ParamField>

## Response

<ResponseField name="data" type="object">
  <Expandable title="Verdict">
    <ResponseField name="verdict.score" type="integer">Inbox score, 0-100.</ResponseField>
    <ResponseField name="verdict.placement" type="string">`inbox`, `promotions` or `spam`.</ResponseField>
    <ResponseField name="verdict.placementConfidence" type="number">Confidence in the placement, 0-1.</ResponseField>
    <ResponseField name="verdict.summary" type="string">One-line summary of the read.</ResponseField>
    <ResponseField name="verdict.signals" type="array">Overall problems judged present.</ResponseField>
    <ResponseField name="verdict.flags" type="array">Flagged sentences, with offsets into `subjectText` or `bodyText`, a category and a suggested action.</ResponseField>
    <ResponseField name="verdict.words" type="array">Flagged words, with a replacement when one is unambiguous.</ResponseField>
    <ResponseField name="model" type="string">Model that produced the verdict.</ResponseField>
    <ResponseField name="subjectText" type="string">The subject as checked. Flag offsets refer to this text.</ResponseField>
    <ResponseField name="bodyText" type="string">The body as checked, as plain text. Flag offsets refer to this text.</ResponseField>
    <ResponseField name="truncated" type="boolean">Whether the body was cut to `maxBodyChars` before checking.</ResponseField>
    <ResponseField name="maxBodyChars" type="integer">Longest body that is checked.</ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
```bash cURL
curl -X POST "https://api.foxreach.io/api/v1/copy/judge" \
  -H "X-API-Key: otr_your_key" \
  -H "Content-Type: application/json" \
  -d '{"subject": "Quick question", "body": "Hi {{firstName}}, are you the right person to talk to about outbound?"}'
```

```python Python (beta)
from foxreach import FoxReach

client = FoxReach(api_key="otr_your_key")
result = client.copy.judge("Quick question", "Hi {{firstName}}, are you the right person to talk to about outbound?")
print(result["verdict"]["score"])
```

```typescript TypeScript (beta)
import { FoxReach } from "foxreach";

const client = new FoxReach({ apiKey: "otr_your_key" });
const result = await client.copy.judge({
  subject: "Quick question",
  body: "Hi {{firstName}}, are you the right person to talk to about outbound?",
});
console.log(result.verdict.score);
```
</RequestExample>

<ResponseExample>
```json
{
  "data": {
    "verdict": {
      "score": 91,
      "placement": "inbox",
      "placementConfidence": 0.9,
      "placementProbabilities": {"inbox": 0.9, "promotions": 0.08, "spam": 0.02},
      "summary": "Reads like a personal note.",
      "signals": [],
      "flags": [],
      "words": []
    },
    "model": "typesafe/jev-latest",
    "subjectText": "Quick question",
    "bodyText": "Hi {{firstName}}, are you the right person to talk to about outbound?",
    "truncated": false,
    "maxBodyChars": 12000
  }
}
```
</ResponseExample>

## Errors

| Status | Description |
|--------|-------------|
| `400` | Neither subject nor body was provided |
| `401` | Missing or invalid API key |
| `502` | The model could not be reached or returned something unusable. Retry shortly |
| `503` | Copy check is unavailable on this server |

## Rate Limit

- **100 requests per minute, shared by every `expensive` call in your workspace.** Fixed 60-second window.
- Every response includes `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (unix epoch seconds) and `X-RateLimit-Bucket` (`expensive`).
- A `429` response includes a `Retry-After` header (seconds until the bucket resets).

For client-side patterns (`Retry-After`, exponential backoff, monitoring `X-RateLimit-Remaining`), see [Rate Limiting](/rate-limiting).
