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.

MethodPathWhat it does
POST/api/submitSend 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/trackAnalytics 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.

Request
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

Response
HTTP 200
{ "ok": true }

Same lead sent twice

Response
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.

Example
HTTP 400
{ "ok": false, "error": "unknown_client" }
StatusErrorWhat it means
400unknown_clientThe client ID does not match an account. Check it under Settings, Developer tools in your dashboard.
400name_and_message_requiredEvery lead needs a name and a message. Other fields are optional.
400invalid_jsonThe request body was not valid JSON. Form-encoded posts are accepted too.
400captcha_failedCaptcha is on for this form and the token was missing or failed. Render the captcha snippet and retry.
400validation_failedA field failed validation. The response lists which fields are missing or invalid so you can fix them.
400invalid_bodyThe hosted form endpoint could not read the request body as JSON or form fields.
405method_not_allowedSubmissions are POST. The read endpoints are GET.
429rate_limitedToo many requests. Back off and retry; limits are listed in the API reference.
502lead_not_saved_or_notifiedWe 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.