API

HTTP JSON API for listing, retrieving, saving, and deleting questionnaires.

Authentication

All API routes require either a signed-in browser session (cookie) or an API key configured on the Admin page.

  • Session — sign in through the web app; subsequent requests from the browser include the session cookie automatically.
  • API key — pass the key in the X-API-Key header or as Authorization: Bearer <key>.

Shell setup

Set these once in your terminal to simplify the examples below:

export API_BASE="https://lead-quote-questionnaire.jkseva.workers.dev"
export API_KEY="your-api-key-from-admin-page"

Every curl example below assumes API_BASE and API_KEY are set. Examples use jq to format JSON output.

Common commands

These three requests cover the most frequent integration workflow: discover available fields, list saved questionnaires, then read responses for one record.

1. List all field keys

Returns index keys (representative, customerName, questionnaireName) plus every response key for a template. Pass templateId to target a specific template; otherwise the default migrated template is used.

curl -s "$API_BASE/api/keys?templateId=TEMPLATE_ID" \
  -H "X-API-Key: $API_KEY" | jq '.keyNames'
Example output
[
  "representative",
  "customerName",
  "questionnaireName",
  "CI-01",
  "CI-02",
  "CI-03",
  "...",
  "CI-25"
]

2. List all questionnaires

Returns a summary row for each saved questionnaire. Use the id from this response in the next step.

curl -s "$API_BASE/api/questionnaires" \
  -H "X-API-Key: $API_KEY" | jq .
Example output
{
  "questionnaires": [
    {
      "id": "da4c0344-a3ac-4228-bd4b-7fa397e62fb5",
      "representative": "tkastle",
      "customerName": "JK Seva",
      "questionnaireName": "Test 1",
      "status": "complete",
      "createdBy": "tkastle",
      "createdAtPST": "2026-08-11 02:12:11 PDT",
      "updatedAtPST": "2026-08-11 08:56:41 PDT",
      "updatedAt": "2026-08-11T15:56:41.966Z"
    }
  ]
}

3. Get responses for one questionnaire

Replace QUESTIONNAIRE_ID with the id from step 2.

export QUESTIONNAIRE_ID="da4c0344-a3ac-4228-bd4b-7fa397e62fb5"

curl -s "$API_BASE/api/questionnaires/$QUESTIONNAIRE_ID" \
  -H "X-API-Key: $API_KEY" | jq '.questionnaire.responses'
Example output
{
  "CI-01": "JK Seva inc.",
  "CI-02": "Technical",
  "CI-03": "10000",
  "CI-04": "Contract / License termination",
  "CI-05": "Today",
  "CI-06": [
    "UKG Dimensions / Pro WFM",
    "UKG Ready",
    "TeleStaff"
  ],
  "CI-07": "",
  "CI-08": "HR, Payroll",
  "CI-09": "Jan 2023 - present",
  "CI-10": "All Employees",
  "...": "..."
}

Checkbox answers (e.g. CI-06) are returned as JSON arrays. Text and select answers are strings. Unanswered questions may be omitted or returned as an empty string.

Template endpoints

Templates define questionnaire structure. Instances reference a template via meta.templateId.

List templates

curl -s "$API_BASE/api/templates" \
  -H "X-API-Key: $API_KEY" | jq '.templates[] | {id, name, status, questionCount}'

List active (non-archived) templates

curl -s "$API_BASE/api/templates/active" \
  -H "X-API-Key: $API_KEY" | jq .

Get one template (includes sections)

curl -s "$API_BASE/api/templates/TEMPLATE_ID" \
  -H "X-API-Key: $API_KEY" | jq .

Create, clone, update, archive

# Create blank template
curl -s -X POST "$API_BASE/api/templates" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -d '{"name":"Custom Discovery"}' | jq .

# Clone existing template
curl -s -X POST "$API_BASE/api/templates/TEMPLATE_ID/clone" \
  -H "X-API-Key: $API_KEY" | jq .

# Download CSV import template
curl -s "$API_BASE/api/templates/csv-template" \
  -H "X-API-Key: $API_KEY" -o questionnaire-template.csv

# Import template from CSV
curl -s -X POST "$API_BASE/api/templates/import-csv" \
  -H "X-API-Key: $API_KEY" \
  -F "file=@questionnaire-template.csv" \
  -F "name=My Imported Template" | jq .

# Archive / unarchive
curl -s -X PUT "$API_BASE/api/templates/TEMPLATE_ID/archive" \
  -H "X-API-Key: $API_KEY" | jq .
curl -s -X PUT "$API_BASE/api/templates/TEMPLATE_ID/unarchive" \
  -H "X-API-Key: $API_KEY" | jq .

Locked templates (already used) return HTTP 403 on update with message: “This template has already been used and cannot be modified. Please clone it instead.”

More curl examples

Full field key metadata

Includes labels, types, and section names for each key — useful when building integrations or reports.

curl -s "$API_BASE/api/keys" \
  -H "X-API-Key: $API_KEY" | jq .

List questionnaire IDs only

curl -s "$API_BASE/api/questionnaires" \
  -H "X-API-Key: $API_KEY" | jq '.questionnaires[].id'

Get index fields for one questionnaire

curl -s "$API_BASE/api/questionnaires/$QUESTIONNAIRE_ID" \
  -H "X-API-Key: $API_KEY" | jq '.questionnaire.index'
Example output
{
  "representative": "tkastle",
  "customerName": "JK Seva",
  "questionnaireName": "Test 1"
}

Get full questionnaire record

Returns id, index, meta, and responses.

curl -s "$API_BASE/api/questionnaires/$QUESTIONNAIRE_ID" \
  -H "X-API-Key: $API_KEY" | jq '.questionnaire'

Get one answer by question ID

curl -s "$API_BASE/api/questionnaires/$QUESTIONNAIRE_ID" \
  -H "X-API-Key: $API_KEY" | jq '.questionnaire.responses["CI-03"]'

Create a new draft questionnaire

When using an API key, include index.representative and templateId in the request body.

curl -s -X POST "$API_BASE/api/questionnaires" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -d '{
    "templateId": "TEMPLATE_ID",
    "index": {
      "representative": "jsmith",
      "customerName": "Acme Corp",
      "questionnaireName": "Initial Discovery"
    },
    "responses": {
      "CI-01": "Acme Corporation",
      "CI-03": "1,500 active employees"
    },
    "status": "draft",
    "currentStep": 2
  }' | jq .

Update an existing questionnaire

Include the questionnaire id to update in place.

curl -s -X POST "$API_BASE/api/questionnaires" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -d '{
    "id": "'"$QUESTIONNAIRE_ID"'",
    "index": {
      "representative": "tkastle",
      "customerName": "JK Seva",
      "questionnaireName": "Test 1"
    },
    "responses": {
      "CI-03": "12000"
    },
    "status": "draft",
    "currentStep": 4
  }' | jq .

Mark a questionnaire complete

curl -s -X POST "$API_BASE/api/questionnaires" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -d '{
    "id": "'"$QUESTIONNAIRE_ID"'",
    "index": {
      "representative": "tkastle",
      "customerName": "JK Seva",
      "questionnaireName": "Test 1"
    },
    "responses": {},
    "status": "complete"
  }' | jq '.questionnaire.meta.status'

Delete a questionnaire

curl -s -X DELETE "$API_BASE/api/questionnaires/$QUESTIONNAIRE_ID" \
  -H "X-API-Key: $API_KEY" | jq .

Endpoint reference

Base URL: https://lead-quote-questionnaire.jkseva.workers.dev/api

GET /api/keys

All index and response field keys with metadata. See Common commands above.

GET /api/questionnaires

List all saved questionnaires (summary only: id, representative, customer, name, status, timestamps).

GET /api/questionnaires/:id

Retrieve one questionnaire with full index, meta, and responses.

POST /api/questionnaires

Create or update a questionnaire. Omit id to create; include id to update.

Request body
{
  "id": "optional-uuid-for-update",
  "index": {
    "representative": "jsmith",
    "customerName": "Acme Corp",
    "questionnaireName": "Initial Discovery"
  },
  "responses": {
    "CI-01": "Answer text…",
    "CI-06": ["Option A", "Option B"]
  },
  "status": "draft",
  "currentStep": 3
}

When using a browser session, index.representative is set automatically from the signed-in user.

DELETE /api/questionnaires/:id

Delete a questionnaire. API key access can delete any record.

Send to Customer

Authenticated routes for creating and managing customer review links. Requires session cookie or API key.

GET /api/questionnaires/:id/send-info

Returns questionnaire summary, subject options, and send/open stats for the send form.

POST /api/questionnaires/:id/send

Create a customer access link and send email. Body: customerFirstName, customerLastName, customerEmail, subject, emailBody.

curl -s -X POST "$API_BASE/api/questionnaires/$QUESTIONNAIRE_ID/send" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "customerFirstName": "Jane",
    "customerLastName": "Doe",
    "customerEmail": "jane@example.com",
    "subject": "JK Seva – Please Review & Complete Your Discovery Questionnaire",
    "emailBody": "Dear {{customerName}} Team,\n\n..."
  }' | jq .
PUT /api/customer-links/:token/{complete|revoke|reactivate}

Update link status. Sender or admin only.

DELETE /api/customer-links/:token

Permanently delete a customer access link. Sender or admin only.

Customer access (public)

No authentication required. Token in URL grants access while the link is active.

GET /api/customer/q/:token

Load questionnaire for customer review. Increments open count. Returns inactive: true when completed or revoked.

POST /api/customer/q/:token

Save customer responses. Body: { "responses": { "CI-01": "…" } }

Admin: customer links & SMTP

GET /api/admin/customer-links

List all customer access links with tracking metadata.

GET /api/admin/smtp

Read SMTP settings (password masked).

PUT /api/admin/smtp

Save SMTP host, port, credentials, from address, and TLS preference.

POST /api/admin/smtp/test

Test SMTP connection and authentication. Returns a detailed debug log (password never included).

Errors

Errors return JSON with an error message and an HTTP status code.

  • 401 — missing or invalid API key / session
  • 403 — not allowed to delete this questionnaire
  • 404 — questionnaire not found
  • 400 — validation error (missing index fields, etc.)
  • 503 — storage not configured
curl -s "$API_BASE/api/questionnaires/not-a-real-id" \
  -H "X-API-Key: $API_KEY" | jq .
Example error
{ "error": "Questionnaire not found" }