---
title: API · MailWeft
description: Check an email from your app.
---

Send the raw email to `POST /v1/check`. The answer is a stream of JSON lines, one per check, sent as each check finishes. A `verdict` line arrives with the first check that decides, and again whenever a later check changes the verdict. The last line, `done`, has the final verdict.

## Usage

```sh
curl -sN --data-binary @message.eml "$MAILWEFT_API/v1/check?llm=off"
```

Read the stream line by line in JavaScript:

```js
const response = await fetch(`${api}/v1/check?llm=auto`, { method: 'POST', body: raw });
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = '';
for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += value;
  const lines = buffer.split('\n');
  buffer = lines.pop();
  for (const line of lines.filter(Boolean)) {
    const event = JSON.parse(line);
    if (event.event === 'verdict') show(event.verdict, event.reason); // first answer, then any change
    if (event.event === 'done') finish(event.verdict);
  }
}
```

## Request

| Part | Value |
| --- | --- |
| Body | The raw email, with headers. At most 1 MB. |
| `llm` | `auto` (default) runs the judge when needed. `always` runs it on every email. `off` never runs it. |

The judge may run 30 times an hour for each address. Only runs count; a check where the judge is not needed does not. After that the judge is skipped and the `judge` line says why.

## Events

Events arrive in this order. A `verdict` event can appear after any check that changes the verdict.

| Event | Fields |
| --- | --- |
| `parsed` | `subject`, `from`, `chars`, `auth` (SPF, DKIM and DMARC results), `links` |
| `rules` | `flag`, `claimed` (organisations the email claims to be), `reasons` |
| `b2` | `score`, `logit`, `model_ms` |
| `b3` | `score`, `band`, `model`, `flag_at`, `safe_below`, `raises`, `lowers` |
| `memory` | `matches`, each with `level`, `class`, `harmful`, `score`, `date`, `source`, `start` and `campaign`; `unavailable` when the server has no memory |
| `b2_sentences` | `sentences`, each with the text and the score drop when it is removed |
| `judge` | `ran`, `verdict`, `confidence`, `reason`, `quotes`, `quotes_verified`, `model_ms`, or `skipped` with the reason |
| `verdict` | `verdict` (`phishing`, `unsure` or `safe`), `by` (the check that decided), `reason`, `first` |
| `done` | The final `verdict` |

Every event except `error` has `ms`, the time since the request arrived on the server.

## Errors

| Status | Message |
| --- | --- |
| 400 | Empty email. Paste an email or open a sample. |
| 413 | Email is larger than 1 MB. Remove attachments and try again. |

An error during a check arrives as an `error` event in the stream.

## Health

`GET /v1/health` returns the loaded models, the number of memory entries, and whether the judge is configured.
