API reference
The API, exactly as it behaves.
Base URL for everything below: https://forms.secondshift.site. These are the same endpoints the dashboard itself calls. If something here ever disagrees with the live service, the live service wins and we fix the docs.
Authentication
Two kinds of access, kept separate on purpose.
Sending data in uses your client ID. The public endpoints (lead submit, analytics beacons, hosted forms) accept it in the request so a browser on your site can deliver a lead or a pageview to your account.
A client ID is an identifier, not authorization. It works like the address on a mailbox: anyone can drop a lead in, and nobody can read anything back with it. It is printed in your page source on purpose, and it is never accepted as proof of identity, alone or in a header.
Reading data out (leads, analytics, settings) requires your signed-in owner session, the same session your dashboard uses: sign in by email link, then send the session as the ss_session cookie or an Authorization: Bearer header against the read endpoints below. A request without a valid session gets a not_found response, never data. There is no separate API key to manage: your session is the credential.
Endpoints
Every endpoint these docs rely on, nothing aspirational.
| Method | Path | What it does |
|---|---|---|
| POST | /api/submit | Send a lead. JSON body or form fields. Requires client, name, and message. |
| POST | /api/f/{clientId} | Hosted form endpoint. The client ID travels in the path. Named forms: /api/f/{clientId}/{formName}. |
| POST | /api/track | Analytics beacons. Sent by t.js; listed so you know what your pages call. |
| GET | /api/snippet?client={clientId} | The optional captcha snippet script for a form that already posts to Second Shift. |
| GET | /api/app/{clientId}/leads SESSION | Your leads, newest first, paginated with limit and offset. |
| GET | /api/app/{clientId}/analytics SESSION | Your analytics summary for the last 30 days. |
Send a lead
The same call the Lead forms page makes.
curl -X POST https://forms.secondshift.site/api/submit \
-H "Content-Type: application/json" \
-d '{
"client": "YOUR_CLIENT_ID",
"name": "Jamie Rivera",
"email": "jamie@example.com",
"phone": "555-010-2233",
"message": "Need a quote for a full repipe.",
"service": "Repiping"
}'Success
HTTP 200
{ "ok": true }Same lead sent twice
HTTP 200
{ "ok": true, "duplicate": true, "leadId": "..." }Resending the same lead merges into the existing one instead of creating a duplicate, and says so. Any field beyond the standard ones (the example adds service) is stored on the lead and shown in your dashboard.
Errors
Failures are JSON too, with a stable error code.
HTTP 400
{ "ok": false, "error": "unknown_client" }| Status | Error | What it means |
|---|---|---|
| 400 | unknown_client | The client ID does not match an account. Check it under Settings, Developer tools in your dashboard. |
| 400 | name_and_message_required | Every lead needs a name and a message. Other fields are optional. |
| 400 | invalid_json | The request body was not valid JSON. Form-encoded posts are accepted too. |
| 400 | captcha_failed | Captcha is on for this form and the token was missing or failed. Render the captcha snippet and retry. |
| 400 | validation_failed | A field failed validation. The response lists which fields are missing or invalid so you can fix them. |
| 400 | invalid_body | The hosted form endpoint could not read the request body as JSON or form fields. |
| 405 | method_not_allowed | Submissions are POST. The read endpoints are GET. |
| 429 | rate_limited | Too many requests. Back off and retry; limits are listed in the API reference. |
| 502 | lead_not_saved_or_notified | We could not save the lead or send its alert. Safe to retry the same submission. |
One quiet exception: if a submission trips the honeypot spam trap, the API answers { "ok": true } and saves nothing. Bots get a success message; you get a clean inbox.
Rate limits
- Lead submits: 60 requests per minute per IP address.
- Hosted form endpoints: 100 submissions per hour per client ID.
- Over the limit, the API answers
429 rate_limited. Back off and retry; a real contact form never comes close.
Webhooks
Outbound webhooks are not available yet, and we would rather say so than document events that never fire. Today a new lead reaches you by instant email alert and is in your dashboard and the session API the moment it is saved. When webhooks ship, this page will list the real event names and payloads, verified against the live service, before we announce them.