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://questionnaire.jkseva.com"
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), meta keys (offeringKey, offeringLabel), and 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",
  "offeringKey",
  "offeringLabel",
  "CI-01",
  "CI-02",
  "CI-03",
  "...",
  "CI-25"
]

The full /api/keys response also includes a meta array describing offering fields and templateOffering with the template’s default offering when templateId is provided.

2. List all questionnaires

Returns a summary row for each saved questionnaire, including offeringKey and offeringLabel. 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",
      "offeringKey": "general_discovery",
      "offeringLabel": "General Discovery",
      "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. Offering values are in questionnaire.meta.

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

curl -s "$API_BASE/api/questionnaires/$QUESTIONNAIRE_ID" \
  -H "X-API-Key: $API_KEY" | jq '.questionnaire.meta | {offeringKey, offeringLabel, status}'
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": [
    "Workforce Central",
    "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. Each template has an offeringKey and offeringLabel used as the default for new questionnaires and customer communications.

Offering keys

Common values: general_discovery (default), infor_solutions, ukg_solutions, data_migration, managed_services, hosting_services, custom_solutions, on_demand_support, other (requires customOffering text).

List templates

curl -s "$API_BASE/api/templates" \
  -H "X-API-Key: $API_KEY" | jq '.templates[] | {id, name, status, offeringKey, offeringLabel, 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","offeringKey":"general_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 (optional offeringKey; defaults to general_discovery)
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" \
  -F "offeringKey=managed_services" | jq .

# Import with JSON body (csv text + offering)
curl -s -X POST "$API_BASE/api/templates/import-csv" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Imported Template","offeringKey":"general_discovery","csv":"section,sectionId,key,label,type\n..."}' | 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.”

CSV files contain question rows only. Offering is template metadata — set offeringKey (and customOffering when other) on import, or edit the template after import.

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 offering and meta for one questionnaire

curl -s "$API_BASE/api/questionnaires/$QUESTIONNAIRE_ID" \
  -H "X-API-Key: $API_KEY" | jq '.questionnaire.meta | {offeringKey, offeringLabel, customOffering, templateId, status}'

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"
    },
    "meta": {
      "offeringKey": "managed_services"
    },
    "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://questionnaire.jkseva.com/api

GET /api/keys

All index, meta, and response field keys with metadata. Includes offeringKey and offeringLabel under meta. See Common commands above.

GET /api/questionnaires

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

GET /api/questionnaires/:id

Retrieve one questionnaire with full index, meta (including offering fields), 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"]
  },
  "meta": {
    "offeringKey": "general_discovery",
    "customOffering": null
  },
  "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, resolved offeringLabel, subject options (with {{offering}} tokens), default email template, 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. Subject options and the default body support {{offering}}, resolved from the questionnaire (or its template). On success, the response includes emailSent and representativeNotified. When the customer email is sent, the questionnaire representative also receives a confirmation email (subject prefix [JKS Questionnaire]) with questionnaire details, next steps, the customer link, and a link to review responses.

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 {{offering}} Questionnaire",
    "emailBody": "Dear {{customerName}} Team,\n\nThank you for meeting with us. {{representative}} captured your {{offering}} requirements under \"{{questionnaireName}}\".\n\n{{formLink}}"
  }' | jq .
POST /api/questionnaires/share

Email a completed questionnaire as an attachment. Body: questionnaireId, to, subject, message, format (html, txt, md, or json). Only questionnaires with status complete may be shared. The attachment is generated server-side. Returns { ok: true } on success. Failures return a generic error message.

curl -s -X POST "$API_BASE/api/questionnaires/share" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "questionnaireId": "'"$QUESTIONNAIRE_ID"'",
    "to": "recipient@example.com",
    "subject": "Completed questionnaire for review",
    "message": "Please find the completed questionnaire attached.",
    "format": "html"
  }' | 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" }