JavaScript (fetch)
Submit forms with the browser's fetch API for AJAX-style submissions: no page reload, and a JSON response you can act on.
How JSON responses work
The endpoint returns JSON instead of a redirect whenever the request asks for it. Send the Accept: application/json header (or post a JSON body) and a successful submission responds with:
{ "ok": true, "submissionId": "sub_abc123" }An error responds with a non-2xx status and a body like:
{
"error": "validation_error",
"message": "One or more fields are invalid.",
"fieldErrors": { "email": "Enter a valid email address." }
}fieldErrors is only present for validation failures, and maps each rejected field name to a message you can render next to the input.
Posting FormData
The most direct approach is to build a FormData from your existing form and post it. Don't set Content-Type yourself — the browser adds the correct multipart boundary for you. Keep the hidden _honey field in your markup; it's captured along with everything else.
const form = document.querySelector("#contact");
form.addEventListener("submit", async (event) => {
event.preventDefault();
const response = await fetch(
"https://api.formcrest.com/v1/submit/abc123",
{
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form),
},
);
const result = await response.json();
if (response.ok && result.ok) {
// Success — result.submissionId is available.
console.log("Submitted:", result.submissionId);
form.reset();
} else {
// Failure — result.error and result.message describe what went wrong.
console.error(result.error, result.message);
if (result.fieldErrors) {
for (const [field, message] of Object.entries(result.fieldErrors)) {
console.warn(field, message);
}
}
}
});Posting JSON
You can also send a JSON body directly. Set Content-Type: application/json and the response is JSON automatically.
const response = await fetch(
"https://api.formcrest.com/v1/submit/abc123",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
email: "test@example.com",
message: "Hello",
}),
},
);
const result = await response.json();Idempotency-Key for safe retries
If you retry a submission after a network hiccup, you can avoid creating a duplicate by sending an Idempotency-Key header. Reuse the same key for the retry and we dedupe within a 24-hour window, returning the original result instead of recording a second submission.
const idempotencyKey = crypto.randomUUID();
await fetch("https://api.formcrest.com/v1/submit/abc123", {
method: "POST",
headers: {
Accept: "application/json",
"Idempotency-Key": idempotencyKey,
},
body: new FormData(form),
});Access key
If the form requires an access key, include it as an access_key field in your FormData or JSON body.
Next steps
- Submit API reference — the full endpoint contract, headers, and response shapes.
- Error codes — every
errorvalue and what it means. - React & Next.js — the same flow wrapped in the
@formcrest/sdkpackage and theuseFormSubmithook.