Integrate Trazo in one call
Trazo accepts a single JSON POST per form submission. No SDK, no email service, no server-side logic on your end. Send the form data to your site's endpoint and Trazo handles validation, bot checks, storage, and email delivery.
Quickstart
Every site gets an API key starting with sk_live_…. POST your form data as JSON with the key, the Cap token, and a submission object. The name, email, and message fields are required, and any extra fields go in custom_fields as flat string values.
POST https://gettrazo.app/api/v1/submissions
Content-Type: application/json
{
"api_key": "sk_live_abc123…",
"cap_token": "abc123…",
"submission": {
"name": "Jane Doe",
"email": "jane@example.com",
"message": "I'd like a quote for my project.",
"custom_fields": { "phone": "555-867-5309" }
}
}
Accepted submissions are stored and emailed to your site's notification address from forms@gettrazo.app. All Trazo email comes from the gettrazo.app domain.
Astro template
The fastest start is the gettrazo/astro-template starter, an Astro site with ContactForm.astro already wired to Trazo.
git clone https://github.com/gettrazo/astro-template my-site
cd my-site
npm install
npm run dev
Set API_KEY and CAP_API_ENDPOINT in src/config.ts and the form works on localhost; then build out the rest of the site. Keep the form's name, email, and message field names, since the endpoint expects them.
Plain HTML
No framework needed. Paste this into any page and it works. Replace the Cap endpoint and the API key with your site's values; the name, email, and message fields are all the endpoint requires.
<!-- Cap renders the bot check; Trazo verifies its token server-side -->
<script type="module" src="https://cdn.jsdelivr.net/npm/cap-widget@0.1.56"></script>
<form id="contact-form">
<input name="name" type="text" required maxlength="200" />
<input name="email" type="email" required />
<textarea name="message" required maxlength="5000"></textarea>
<!-- Replace with your site's Cap endpoint (shown on your site's page) -->
<cap-widget data-cap-api-endpoint="https://cap.gettrazo.app/YOUR_CAP_SITE_KEY/"></cap-widget>
<button type="submit">Send</button>
<p id="contact-status" hidden></p>
</form>
<script>
const form = document.getElementById("contact-form");
const status = document.getElementById("contact-status");
form.addEventListener("submit", async (event) => {
event.preventDefault();
status.hidden = true;
const res = await fetch("https://gettrazo.app/api/v1/submissions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
api_key: "sk_live_your_api_key_here",
cap_token: form.querySelector('[name="cap-token"]')?.value,
submission: {
name: form.name.value,
email: form.email.value,
message: form.message.value,
},
}),
});
const data = await res.json();
status.hidden = false;
if (data.status === "ok") {
status.textContent = "Thanks! Your message is on its way.";
form.reset();
} else {
status.textContent = data.message || "Something went wrong. Please try again.";
document.querySelector("cap-widget")?.reset();
}
});
</script>
Response codes
Every response is JSON. Success returns { "status": "ok" }; errors return { "status": "error", "message": "…" }. Handle these to show your own success or error state.
| Status | Meaning | Cause |
|---|---|---|
| 200 | Submission accepted | Stored and queued for email delivery. |
| 401 | Invalid API key | The key is wrong, revoked, or the site is inactive. |
| 403 | Origin not allowed | The request came from a domain not registered for the site. |
| 422 | Bot verification failed | The Cap token was missing, invalid, or already used. Reset the widget and retry. |
| 422 | Validation failed | A required field is missing or malformed. An errors object maps fields to messages. |
| 429 | Rate limited | Too many submissions in a short window. Ask the user to wait and retry. |