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-08-27.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
}
}{
"error": {
"code": "INVALID_REQUEST",
"type": "invalid_request",
"message": "Request validation failed.",
"suggestion": "Fix the field reported in `param` and retry.",
"docs": "https://docs.brew.new/api-reference/api/errors",
"param": "emailHtml"
}
}{
"error": {
"code": "INVALID_API_KEY",
"type": "authentication_error",
"message": "The provided API key is invalid.",
"suggestion": "Check the API key format and retry with a valid active key.",
"docs": "https://docs.brew.new/api-reference/api/authentication"
}
}{
"error": {
"code": "INSUFFICIENT_CREDITS",
"type": "payment_required",
"message": "This operation required more credits than the 0 remaining on the 'free' plan. See the per-operation cost in GET /v1/help.",
"suggestion": "Upgrade your plan or wait for the next billing period to reset. Check your balance up front with GET /v1/usage.",
"docs": "https://docs.brew.new/api-reference/api/credits",
"details": {
"cost": 5,
"remaining": 0,
"planKey": "free"
}
}
}{
"error": {
"code": "INSUFFICIENT_PERMISSIONS",
"type": "authorization_error",
"message": "The caller does not have the required permission.",
"suggestion": "Use an API key or session with the required permission.",
"docs": "https://docs.brew.new/api-reference/api/authentication",
"param": "emails"
}
}{
"error": {
"code": "IDEMPOTENCY_CONFLICT",
"type": "conflict",
"message": "The same idempotency key was reused with a different request payload.",
"suggestion": "Reuse the original payload or send a new idempotency key.",
"docs": "https://docs.brew.new/api-reference/api/idempotency"
}
}{
"error": {
"code": "PAYLOAD_TOO_LARGE",
"type": "invalid_request",
"message": "Request body must not exceed 6291456 bytes.",
"suggestion": "Reduce the payload size and retry.",
"docs": "https://docs.brew.new/api-reference/api/errors"
}
}{
"error": {
"code": "RATE_LIMITED",
"type": "rate_limit",
"message": "Too many requests.",
"suggestion": "Wait for the retry window before sending another request.",
"docs": "https://docs.brew.new/api-reference/api/rate-limits",
"retryAfter": 42
}
}{
"error": {
"code": "INTERNAL_ERROR",
"type": "internal_error",
"message": "An unexpected error occurred.",
"suggestion": "Retry the request. If it keeps failing, contact support.",
"docs": "https://docs.brew.new/api-reference/api/errors"
}
}{
"error": {
"code": "SERVICE_UNAVAILABLE",
"type": "service_unavailable",
"message": "Your credit balance could not be verified because a billing dependency is temporarily unavailable.",
"suggestion": "Retry the request after a short delay.",
"docs": "https://docs.brew.new/api-reference/api/credits",
"retryAfter": 2
}
}Audit an email
Lint raw email content for production readiness. emailHtml is capped at 5,000,000 UTF-8 bytes and the complete JSON body at 6 MiB; subject and previewText each have a 1,000-character transport cap. Omitted preview text is extracted from the authored preheader, while an explicit empty string stays empty. Omitted sendingPurpose defaults to marketing and is reported as defaulted. Independent checks run in parallel across unsubscribe compliance, links and images, total loaded size, accessibility, markup, subject line, and preview text. The stable versioned response reports every check, up to 100 normalized findings, exact totals, metrics, and a nested completion discriminator. A complete result has a 0–100 score and costs 5 credits (X-Credit-Cost: 5). If a required lane is unavailable, the endpoint returns a partial result with score: null, never establishes readiness, costs 0 credits (X-Credit-Cost: 0), and releases the idempotency key so the same key can retry. Admission is limited to 6 requests per minute per credential or session and 20 per minute across the organization, shared by public API, MCP, and agent calls. Brew runs at most 4 audits concurrently per organization and 16 globally; capacity rejections return 429 RATE_LIMITED with Retry-After and do not run or charge the audit.
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-08-27.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
}
}{
"error": {
"code": "INVALID_REQUEST",
"type": "invalid_request",
"message": "Request validation failed.",
"suggestion": "Fix the field reported in `param` and retry.",
"docs": "https://docs.brew.new/api-reference/api/errors",
"param": "emailHtml"
}
}{
"error": {
"code": "INVALID_API_KEY",
"type": "authentication_error",
"message": "The provided API key is invalid.",
"suggestion": "Check the API key format and retry with a valid active key.",
"docs": "https://docs.brew.new/api-reference/api/authentication"
}
}{
"error": {
"code": "INSUFFICIENT_CREDITS",
"type": "payment_required",
"message": "This operation required more credits than the 0 remaining on the 'free' plan. See the per-operation cost in GET /v1/help.",
"suggestion": "Upgrade your plan or wait for the next billing period to reset. Check your balance up front with GET /v1/usage.",
"docs": "https://docs.brew.new/api-reference/api/credits",
"details": {
"cost": 5,
"remaining": 0,
"planKey": "free"
}
}
}{
"error": {
"code": "INSUFFICIENT_PERMISSIONS",
"type": "authorization_error",
"message": "The caller does not have the required permission.",
"suggestion": "Use an API key or session with the required permission.",
"docs": "https://docs.brew.new/api-reference/api/authentication",
"param": "emails"
}
}{
"error": {
"code": "IDEMPOTENCY_CONFLICT",
"type": "conflict",
"message": "The same idempotency key was reused with a different request payload.",
"suggestion": "Reuse the original payload or send a new idempotency key.",
"docs": "https://docs.brew.new/api-reference/api/idempotency"
}
}{
"error": {
"code": "PAYLOAD_TOO_LARGE",
"type": "invalid_request",
"message": "Request body must not exceed 6291456 bytes.",
"suggestion": "Reduce the payload size and retry.",
"docs": "https://docs.brew.new/api-reference/api/errors"
}
}{
"error": {
"code": "RATE_LIMITED",
"type": "rate_limit",
"message": "Too many requests.",
"suggestion": "Wait for the retry window before sending another request.",
"docs": "https://docs.brew.new/api-reference/api/rate-limits",
"retryAfter": 42
}
}{
"error": {
"code": "INTERNAL_ERROR",
"type": "internal_error",
"message": "An unexpected error occurred.",
"suggestion": "Retry the request. If it keeps failing, contact support.",
"docs": "https://docs.brew.new/api-reference/api/errors"
}
}{
"error": {
"code": "SERVICE_UNAVAILABLE",
"type": "service_unavailable",
"message": "Your credit balance could not be verified because a billing dependency is temporarily unavailable.",
"suggestion": "Retry the request after a short delay.",
"docs": "https://docs.brew.new/api-reference/api/credits",
"retryAfter": 2
}
}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
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?