curl --request POST \
--url https://brew.new/api/v1/emails/audit \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"emailHtml": "<!doctype html><html lang=\"en\"><body><a href=\"https://example.com/account\">View account</a><a href=\"{{ unsubscribe_url }}\">Unsubscribe</a></body></html>",
"subject": "Your August account update",
"previewText": "A quick look at what changed this month.",
"sendingPurpose": "marketing"
}
'import requests
url = "https://brew.new/api/v1/emails/audit"
payload = {
"emailHtml": "<!doctype html><html lang=\"en\"><body><a href=\"https://example.com/account\">View account</a><a href=\"{{ unsubscribe_url }}\">Unsubscribe</a></body></html>",
"subject": "Your August account update",
"previewText": "A quick look at what changed this month.",
"sendingPurpose": "marketing"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
emailHtml: '<!doctype html><html lang="en"><body><a href="https://example.com/account">View account</a><a href="{{ unsubscribe_url }}">Unsubscribe</a></body></html>',
subject: 'Your August account update',
previewText: 'A quick look at what changed this month.',
sendingPurpose: 'marketing'
})
};
fetch('https://brew.new/api/v1/emails/audit', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://brew.new/api/v1/emails/audit",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'emailHtml' => '<!doctype html><html lang="en"><body><a href="https://example.com/account">View account</a><a href="{{ unsubscribe_url }}">Unsubscribe</a></body></html>',
'subject' => 'Your August account update',
'previewText' => 'A quick look at what changed this month.',
'sendingPurpose' => 'marketing'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://brew.new/api/v1/emails/audit"
payload := strings.NewReader("{\n \"emailHtml\": \"<!doctype html><html lang=\\\"en\\\"><body><a href=\\\"https://example.com/account\\\">View account</a><a href=\\\"{{ unsubscribe_url }}\\\">Unsubscribe</a></body></html>\",\n \"subject\": \"Your August account update\",\n \"previewText\": \"A quick look at what changed this month.\",\n \"sendingPurpose\": \"marketing\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://brew.new/api/v1/emails/audit")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"emailHtml\": \"<!doctype html><html lang=\\\"en\\\"><body><a href=\\\"https://example.com/account\\\">View account</a><a href=\\\"{{ unsubscribe_url }}\\\">Unsubscribe</a></body></html>\",\n \"subject\": \"Your August account update\",\n \"previewText\": \"A quick look at what changed this month.\",\n \"sendingPurpose\": \"marketing\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://brew.new/api/v1/emails/audit")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"emailHtml\": \"<!doctype html><html lang=\\\"en\\\"><body><a href=\\\"https://example.com/account\\\">View account</a><a href=\\\"{{ unsubscribe_url }}\\\">Unsubscribe</a></body></html>\",\n \"subject\": \"Your August account update\",\n \"previewText\": \"A quick look at what changed this month.\",\n \"sendingPurpose\": \"marketing\"\n}"
response = http.request(request)
puts response.read_body{
"schemaVersion": 1,
"rulesetVersion": "2026-10-02.1",
"auditId": "00000000-0000-4000-8000-000000000001",
"contentHash": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
"auditedAt": "2026-08-23T00:00:00.000Z",
"expiresAt": "2026-08-23T00:15:00.000Z",
"policy": {
"purpose": "marketing",
"source": "provided",
"unsubscribe": "required"
},
"summary": {
"blockers": 0,
"errors": 0,
"warnings": 1,
"info": 0,
"total": 1
},
"checks": [
{
"id": "preflight",
"status": "issues",
"durationMs": 0,
"findingCount": 1
}
],
"metrics": {
"htmlBytes": 2134,
"linkCount": 2,
"imageCount": 0,
"gifCount": 0,
"loadedSize": {
"status": "exact",
"htmlBytes": 2134,
"remoteAssetBytes": 0,
"totalBytes": 2134,
"assetCount": 0
}
},
"findings": [
{
"id": "copy.subject.long:subject",
"ruleId": "copy.subject.long",
"category": "copy",
"severity": "warning",
"impact": "advisory",
"message": "The subject may truncate on smaller inboxes.",
"remediation": "Shorten it while keeping the main benefit clear.",
"sources": [
"preflight"
],
"target": {
"kind": "subject"
}
}
],
"totalFindings": 1,
"findingsTruncated": false,
"completion": {
"status": "complete",
"readiness": "needs_review",
"score": 97
}
}Audit an email
Audits an email for production readiness in parallel: links, images, compliance, loaded size, client support, accessibility and judged copy, plus markup checks.
Use when a design is about to ship, or to score imported HTML or JSX before a send. A saved design is addressed by emailId and rendered exactly as a send renders it.
Input exactly one of emailHtml, emailJsx (React Email, rendered by Brew) or emailId (+ optional emailVersionId), each up to 5,000,000 UTF-8 bytes (the JSON body up to 6 MiB); optional subject and previewText (each up to 1,000 characters; an omitted subject is not judged, an omitted preview is extracted from the authored preheader, an explicit empty string stays empty); optional sendingPurpose (when omitted, the audit infers it from the content and reports it as inferred, or as defaulted to marketing when unsure).
Returns 200 with a stable versioned { findings, checks, metrics, summary, completion }: every check, up to 100 normalized findings (each with optional evidence: an HTTP status, the clients that drop a feature, or a judgment probability), exact totals. completion.status: 'complete' carries a 0 to 100 score and costs 5 credits (X-Credit-Cost: 5); 'partial' (a required lane was unavailable) has score: null, never establishes readiness, costs 0 credits (X-Credit-Cost: 0) and releases the idempotency key so the same key can retry.
Errors 429 RATE_LIMITED with Retry-After when admission is exhausted: 6 requests per minute per credential or session and 20 per minute across the organization (shared by public API, MCP and agent calls), and at most 4 audits concurrently per organization and 16 globally; a capacity rejection never runs or charges the audit. 404 EMAIL_NOT_FOUND or EMAIL_VERSION_NOT_FOUND when a stored design is named and does not exist; 422 CONTENT_OPERATION_FAILED when the HTML cannot be processed.
See also previewEmailAcrossClients, createInboxPlacementTest.
curl --request POST \
--url https://brew.new/api/v1/emails/audit \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"emailHtml": "<!doctype html><html lang=\"en\"><body><a href=\"https://example.com/account\">View account</a><a href=\"{{ unsubscribe_url }}\">Unsubscribe</a></body></html>",
"subject": "Your August account update",
"previewText": "A quick look at what changed this month.",
"sendingPurpose": "marketing"
}
'import requests
url = "https://brew.new/api/v1/emails/audit"
payload = {
"emailHtml": "<!doctype html><html lang=\"en\"><body><a href=\"https://example.com/account\">View account</a><a href=\"{{ unsubscribe_url }}\">Unsubscribe</a></body></html>",
"subject": "Your August account update",
"previewText": "A quick look at what changed this month.",
"sendingPurpose": "marketing"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
emailHtml: '<!doctype html><html lang="en"><body><a href="https://example.com/account">View account</a><a href="{{ unsubscribe_url }}">Unsubscribe</a></body></html>',
subject: 'Your August account update',
previewText: 'A quick look at what changed this month.',
sendingPurpose: 'marketing'
})
};
fetch('https://brew.new/api/v1/emails/audit', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://brew.new/api/v1/emails/audit",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'emailHtml' => '<!doctype html><html lang="en"><body><a href="https://example.com/account">View account</a><a href="{{ unsubscribe_url }}">Unsubscribe</a></body></html>',
'subject' => 'Your August account update',
'previewText' => 'A quick look at what changed this month.',
'sendingPurpose' => 'marketing'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://brew.new/api/v1/emails/audit"
payload := strings.NewReader("{\n \"emailHtml\": \"<!doctype html><html lang=\\\"en\\\"><body><a href=\\\"https://example.com/account\\\">View account</a><a href=\\\"{{ unsubscribe_url }}\\\">Unsubscribe</a></body></html>\",\n \"subject\": \"Your August account update\",\n \"previewText\": \"A quick look at what changed this month.\",\n \"sendingPurpose\": \"marketing\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://brew.new/api/v1/emails/audit")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"emailHtml\": \"<!doctype html><html lang=\\\"en\\\"><body><a href=\\\"https://example.com/account\\\">View account</a><a href=\\\"{{ unsubscribe_url }}\\\">Unsubscribe</a></body></html>\",\n \"subject\": \"Your August account update\",\n \"previewText\": \"A quick look at what changed this month.\",\n \"sendingPurpose\": \"marketing\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://brew.new/api/v1/emails/audit")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"emailHtml\": \"<!doctype html><html lang=\\\"en\\\"><body><a href=\\\"https://example.com/account\\\">View account</a><a href=\\\"{{ unsubscribe_url }}\\\">Unsubscribe</a></body></html>\",\n \"subject\": \"Your August account update\",\n \"previewText\": \"A quick look at what changed this month.\",\n \"sendingPurpose\": \"marketing\"\n}"
response = http.request(request)
puts response.read_body{
"schemaVersion": 1,
"rulesetVersion": "2026-10-02.1",
"auditId": "00000000-0000-4000-8000-000000000001",
"contentHash": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
"auditedAt": "2026-08-23T00:00:00.000Z",
"expiresAt": "2026-08-23T00:15:00.000Z",
"policy": {
"purpose": "marketing",
"source": "provided",
"unsubscribe": "required"
},
"summary": {
"blockers": 0,
"errors": 0,
"warnings": 1,
"info": 0,
"total": 1
},
"checks": [
{
"id": "preflight",
"status": "issues",
"durationMs": 0,
"findingCount": 1
}
],
"metrics": {
"htmlBytes": 2134,
"linkCount": 2,
"imageCount": 0,
"gifCount": 0,
"loadedSize": {
"status": "exact",
"htmlBytes": 2134,
"remoteAssetBytes": 0,
"totalBytes": 2134,
"assetCount": 0
}
},
"findings": [
{
"id": "copy.subject.long:subject",
"ruleId": "copy.subject.long",
"category": "copy",
"severity": "warning",
"impact": "advisory",
"message": "The subject may truncate on smaller inboxes.",
"remediation": "Shorten it while keeping the main benefit clear.",
"sources": [
"preflight"
],
"target": {
"kind": "subject"
}
}
],
"totalFindings": 1,
"findingsTruncated": false,
"completion": {
"status": "complete",
"readiness": "needs_review",
"score": 97
}
}Authorizations
Send your Brew API key as Authorization: Bearer brew_xxx.
Headers
Optional idempotency key for safe retries. Reusing the same key with the same request body returns the original response for 24 hours.
1 - 100The brand this request acts on. REQUIRED for organization-scoped credentials (otherwise 400 BRAND_ID_REQUIRED — there is no default brand); list ids with GET /v1/brands. Brand-scoped credentials may omit it, and sending a different brand returns 403 BRAND_SCOPE_MISMATCH. A brand outside your organization returns 404 BRAND_NOT_FOUND.
1 - 64Body
- Option 1
- Option 2
- Option 3
Rendered email HTML.
1Subject line to judge. Omit it and the subject is not evaluated; an empty string is reported as a missing subject.
1000Inbox preview text. Omit it to read the preheader from the email.
1000marketing (unsubscribe link and postal address required) or transactional. Omit it and the audit infers the purpose from the content.
marketing, transactional Response
A complete or partial audit. Branch on completion.status; only complete carries a numeric score. A partial response is not cached under its idempotency key and may be retried with the same key.
1 1 - 100^sha256:[0-9a-f]{64}$Show child attributes
Show child attributes
Show child attributes
Show child attributes
32- Option 1
- Option 2
- Option 3
- Option 4
Show child attributes
Show child attributes
Show child attributes
Show child attributes
100Show child attributes
Show child attributes
x >= 0- Option 1
- Option 2
Show child attributes
Show child attributes
Was this page helpful?