Skip to content

Your first submission

The fastest way to confirm you can talk to Expeed Relay: send one submission using values your admin gave you.

  • siteKey — from your admin (looks like marketing-site-a)
  • formKey — from your admin (looks like contact-us)
  • captchaToken — set to none if the site has no CAPTCHA configured; otherwise a real Google/Cloudflare token (covered in CAPTCHA — Phase 3)

Every submission POSTs to https://api.relay.expeed.com/api/v1/submissions with a JSON body. Below is the same submission in three languages — click a tab.

Terminal window
curl -X POST https://api.relay.expeed.com/api/v1/submissions \
-H "Content-Type: application/json" \
-d '{
"siteKey": "YOUR_SITE_KEY",
"formKey": "YOUR_FORM_KEY",
"captchaToken": "none",
"data": {
"name": "Jane Smith",
"email": "jane@example.com",
"message": "Testing Expeed Relay"
}
}'
  • 201 Created — success. Response body is { "success": true, "submissionId": "<uuid>" }. The submission is now in Expeed Relay’s queue and will be delivered per your form’s configuration (email / WhatsApp / webhook).
  • 400 Bad Request — usually a missing or invalid field. Response has a message explaining which. Also covers payload limits: at most 100 fields, 10,000 characters per value, 128 characters per field name. The field names _subject, _body, and _footer are reserved and rejected.
  • 404 Not Found — message Submission target not found or not permitted. Read the section below before you start debugging this one.
  • 429 Too Many Requests — you have exceeded ten submissions per minute from your IP. Clears by itself after a minute. Easy to hit while testing.
  • 503 Service Unavailable — Expeed Relay couldn’t reach an upstream provider (Google CAPTCHA verify, etc.). Safe to retry. If the message is CAPTCHA is misconfigured for this site, retrying will not help — the site names a CAPTCHA provider but has no secret stored, and an admin has to fix it.

Submission target not found or not permitted is returned for four different causes, with nothing in the response to tell them apart:

  • the siteKey does not match any site
  • the formKey does not match any form on that site
  • the site or the form has been deactivated
  • your request’s Origin is not in the site’s allowed origins list

This is intentional. A more specific error would let anyone probe the API to discover which site keys are real, so the ambiguity is a deliberate anti-enumeration measure rather than a gap in the error handling.

The practical consequence is that an origin problem looks exactly like a typo in your site key. If you are confident the keys are right, do not keep re-checking them — ask your admin to confirm the exact contents of the site’s allowed origins list and compare it against the Origin header your browser is actually sending. An origin is a scheme, host, and optional port with no path and no trailing slash, so https://example.com matches and https://example.com/ does not. Server-to-server calls send no Origin header at all, which passes only when the site’s list is empty.

Ask your admin to check both Active toggles too — a deactivated site or form is indistinguishable from a wrong key from where you are sitting.

Ask your admin to open the Submissions page in the dashboard. Your test should show up within seconds. From there, the Jobs page shows the actual delivery attempt (email sent, webhook fired) and its status.

  • Full HTML example with reCAPTCHA v3 wired up — coming in Phase 3
  • Framework integrations (React, Angular, Vue) — coming in Phase 3
  • Backend integrations with API keys for server-to-server calls — coming in Phase 3
  • All error codes with cause + fix — coming in Phase 3