The enquiry form's address
The one public endpoint that takes an enquiry from any website, with its fields, photos, answers, errors and limits.
Each workspace has one public address that takes an enquiry. It needs no API key, no sign-in and no Idempotency-Key. It is the only action in the API that sends a CORS header, so a script on any website can post to it.
What the business sees and does is in Enquiries from your website.
The address
POST https://app.tradesender.co.uk/api/v1/public/enquiry_forms/YOUR_FORM_KEY
The form key is 24 letters and digits. An owner or office member copies it from Settings, then Website and portal. The API returns it on the account as enquiry_form.key, beside post_url, page_url and script_url.
- Use
https. A post tohttpis redirected and its body is lost. - The address, its field names and
/enquiry.jsare pasted into other people's websites, so they do not change.
The key is not a secret
The key sits in the source of every page that uses the form. All it allows is sending an enquiry to that workspace. It cannot read anything from the workspace.
The workspace's owner can replace it in Settings. The old address then answers 404 at once. An API key cannot replace it.
The request
Send the fields flat, in one of three encodings:
Content-Type | Use it for |
|---|---|
multipart/form-data | A form with photos. It is the only encoding that carries files. |
application/x-www-form-urlencoded | A plain HTML form with no photos. |
application/json | A server, or a form tool's webhook. |
No header is required. Accept decides the shape of the answer: see Answers.
From a browser on another website
Send FormData or a URL-encoded body, and set no header except Accept. A browser sends that without a preflight.
There is no OPTIONS route, so a request that needs a preflight fails. That rules out Content-Type: application/json and any custom header from a browser. Send JSON from a server.
Send no cookies or credentials.
Fields
Every field is optional. The post must hold something: one field with a value, or one file. Fields that start with _ do not count.
| Field | Limit | Also read as |
|---|---|---|
name | 255 characters | full_name, contact_name, customer_name |
first_name with last_name | Joined into name when there is no name | first, forename, fname and last, surname, lname |
email | 255 characters | email_address, mail |
phone | 255 characters | tel, telephone, mobile, phone_number, telephone_number, mobile_number, contact_number |
address_line_1 | 255 characters | address, address1, street, street_address |
city | 255 characters | town, town_city |
postal_code | 255 characters | postcode, zip, zip_code |
message | 5,000 characters | comments, comment, enquiry, details, description, how_can_we_help |
photos[] | 5 files of 10 MB each | Any uploaded file, whatever its field is called |
How a name is matched:
- Capitals, spaces, hyphens, underscores and other punctuation are ignored.
Postal Code,postal-codeandpostalCodeare one field. - A leading
youris dropped, soyour-nameisname. - A hyphen and two or more digits at the end are dropped, so Contact Form 7's
tel-123istel. - Two fields that mean the same thing are joined with a comma.
phoneandmobilearrive as one phone number field holding both.
How a value is stored:
- Nothing is checked. An
emailthat is not an email address is kept as it was sent. - Space at each end is trimmed, and a line break in a one-line field becomes a space.
- A value over its limit is cut. It is not refused.
- With no name, the enquiry is named after the email address, then the phone number, then "Website enquiry".
Other fields
A field that is not in the table is kept at the end of the message as "Label: value", one to a line. The label is the field's name with hyphens and underscores turned into spaces, cut at 60 characters. garden_size=Large becomes "Garden size: Large".
- A field sent more than once, such as a group of tick boxes, gives one line for each value.
- Up to 30 lines are kept.
- In JSON, objects nested up to three deep are read as if their fields were at the top.
- These are left out: any name containing
captcha,turnstile,token,nonceorpassw, and the namessubmit,form_idandform_name.
The 5,000 characters cover the message and these lines together.
Fields that start with an underscore
| Field | What it does |
|---|---|
_gotcha | The honeypot. Hide it from people, and send it empty or leave it out. A post that fills it is answered as a success and nothing is stored. |
_redirect | Where a browser form post goes afterwards. See Where a form post is sent on. |
_page | The address of the page the form is on, shown to the business as where the enquiry came from. Without it the Referer header is used. It is kept only if it starts http:// or https://, and is cut at 255 characters. |
Any other field that starts with _ is ignored. A form tool's own hidden fields stay out of the message.
Photos
- Send photos as files in
multipart/form-data. Name the fieldphotos[], with the brackets. A field calledphotosthat holds several files delivers only the last. - Up to 5 are kept, each 10 MB or less.
- A photo is a JPEG, PNG, WebP, HEIC or HEIF. The type is read from the file's contents. Its name and the type the browser declared are ignored.
- Photos are stored as they arrive. Nothing is resized or converted, so shrink large photos before you send them.
- A file that fails a check is left out and the enquiry is still accepted. The message then ends with a note such as "(2 attached files could not be kept. Photos must be JPEG, PNG, WebP or HEIC, 10 MB at most, and no more than 5.)"
- The whole request may be up to 56 MB.
Answers
The answer depends on the Accept header.
Accept | Status | Body |
|---|---|---|
Contains text/html, as a browser submitting a form sends | 303 | None. Location says where to go. |
| Anything else, or absent | 201 | {"data":{"received":true}} |
The answer does not include the enquiry, an id or anything about the workspace.
Once an enquiry is stored it appears in the workspace as new, and TradeSender emails the business. It sends nothing to the person who enquired.
Where a form post is sent on
Location is the value of _redirect when all of these hold:
- the request has an
Originheader _redirectis that origin, or starts with that origin followed by/_redirecthas no spaces in it
Otherwise Location is the workspace's hosted thank-you page:
https://app.tradesender.co.uk/#/enquiry/YOUR_FORM_KEY?sent=1
So a form can send people back only to the website it was posted from.
Accepted and not stored
Two posts are answered exactly like a success, with nothing new stored and no email:
- A filled honeypot.
_gotchahad a value. - A repeat. The same
name,email,phoneandmessage, with the same number of photos, within 10 minutes of the first. A retry after a timeout is safe.
Errors
Errors use the API's usual envelope:
{ "errors": [{ "code": "validation_failed", "detail": "Add your name and how to reach you" }] }
A plain HTML form is the exception, because the browser would show its visitor that JSON. A request whose Accept has text/html and no JSON type gets a small HTML page: the heading "Something went wrong", the same detail as a sentence, and "Go back and try again." The status and the headers are the same as for the JSON answer, Retry-After included. The page loads nothing and runs no script.
Every other request gets the JSON, so a script that sets Accept: application/json, or sends no Accept at all, sees no change.
| Status | Code | When |
|---|---|---|
| 404 | not_found | The key is not current: it was mistyped, or the owner replaced it. |
| 413 | None | The request is over 56 MB. The web server refuses it before the API reads it, so it has no error envelope and no CORS header. |
| 422 | validation_failed | The post has nothing in it. |
| 429 | rate_limited | A limit below was reached. The answer carries Retry-After, in seconds. |
Limits
| Limit | Counted | Answer |
|---|---|---|
| 10 posts in 15 minutes | For each IP address, across every workspace's form | 429 with Retry-After: 900 |
| 100 enquiries in 24 hours | For each workspace, counting the enquiries it has stored | 429 with Retry-After: 86400 |
| 56 MB | For each request, photos included | 413 |
- Every post counts towards the 10, including a refused one and a filled honeypot.
- A server that forwards for many visitors, such as a webhook or the WordPress plugin's Contact Form 7 forwarding, posts from one IP address. Its visitors share the 10.
Retry-Afteris the length of the limit that was reached, which is the longest you have to wait. The limit may lift sooner: the API does not say when its window began.- There is no captcha.
CORS
Every answer the API gives to the POST carries Access-Control-Allow-Origin: *: 201, 303, 404, 422 and 429. It sends no other CORS header.
Read who a form belongs to
GET https://app.tradesender.co.uk/api/v1/public/enquiry_forms/YOUR_FORM_KEY
{ "data": { "business": { "name": "Green & Co.", "logo_url": null } } }
It answers 404 when the key is not current, which makes it a way to check a key before you save it. It is limited to 60 requests in 5 minutes for each IP address, answering 429 with Retry-After: 300 over that. It sends no CORS header, so call it from a server.
Examples
Each of these creates a real enquiry and emails the business. Delete your tests afterwards.
An HTML form
<form
action="https://app.tradesender.co.uk/api/v1/public/enquiry_forms/YOUR_FORM_KEY"
method="post"
enctype="multipart/form-data"
>
<label>Your name <input name="name" required /></label>
<label>Phone <input name="phone" type="tel" /></label>
<label>Email <input name="email" type="email" /></label>
<label>Postcode <input name="postal_code" /></label>
<label>How can we help? <textarea name="message"></textarea></label>
<label>Photos <input type="file" name="photos[]" accept="image/*" multiple /></label>
<input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off" />
<input type="hidden" name="_redirect" value="https://your-website.example/thanks" />
<button>Send</button>
</form>
A script, with fetch
<form id="enquiry">
<label>Your name <input name="name" required /></label>
<label>Phone <input name="phone" type="tel" required /></label>
<label>How can we help? <textarea name="message"></textarea></label>
<label>Photos <input type="file" name="photos[]" accept="image/*" multiple /></label>
<input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off" />
<button>Send</button>
<p id="enquiry-status" role="status"></p>
</form>
<script>
const form = document.querySelector('#enquiry');
const status = document.querySelector('#enquiry-status');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const body = new FormData(form);
body.append('_page', window.location.href);
try {
const response = await fetch(
'https://app.tradesender.co.uk/api/v1/public/enquiry_forms/YOUR_FORM_KEY',
{ method: 'POST', body, headers: { Accept: 'application/json' } }
);
if (response.ok) {
form.reset();
status.textContent = 'Thanks, your enquiry has been sent.';
} else {
const payload = await response.json().catch(() => null);
status.textContent = payload?.errors?.[0]?.detail ?? 'We could not send that.';
}
} catch {
status.textContent = 'We could not send that. Check your connection and try again.';
}
});
</script>
From a terminal, with a photo
curl "https://app.tradesender.co.uk/api/v1/public/enquiry_forms/YOUR_FORM_KEY" \
-F "name=Morgan Hughes" \
-F "phone=07700 900318" \
-F "postal_code=BS6 5TR" \
-F "message=The back hedge needs cutting back." \
-F "photos[]=@back-hedge.jpg"
JSON from a server
curl "https://app.tradesender.co.uk/api/v1/public/enquiry_forms/YOUR_FORM_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Morgan Hughes", "email": "morgan.hughes@mail.example", "message": "The back hedge needs cutting back.", "garden_size": "Large" }'
Related
- The embed script draws a ready-made form that posts here.
- WordPress plugin reference covers the shortcode and Contact Form 7 forwarding.
- To read enquiries once they have arrived, use the Enquiries operations in the API reference with an API key.
Checked against the app on 18 October 2026. Still stuck? Ask usAsk us.