Skip to main content
Questra’s API lets you build a fully whitelabeled AI survey-programming workflow inside your own product. This guide walks through the complete lifecycle end-to-end: creating a survey, uploading a questionnaire document, triggering AI programming, tracking progress, and exporting the finished survey for your target platform — all without a user ever touching the Questra UI.

Authentication

Every request to the Questra API must include your API key as a Bearer token and your organization ID in the headers:
curl https://api.questra.ai/surveys \
  -H "Authorization: Bearer $QUESTRA_API_KEY" \
  -H "X-Org-Id: $QUESTRA_ORG_ID"
You can create API keys and find your organization ID from the Questra dashboard. See the Authentication guide for step-by-step instructions.

Step 1 — Create a survey

Start by creating an empty survey that will be the container for the AI-programmed content.
curl -X POST https://api.questra.ai/surveys \
  -H "Authorization: Bearer $QUESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Q4 Customer Satisfaction Study"
  }'
A 201 response returns a FullSurvey object. Save the id — you’ll use it in every subsequent step.
{
  "id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
  "slug": "q4-customer-satisfaction-study",
  "name": "Q4 Customer Satisfaction Study",
  "locked": false,
  "programming_started_at": null,
  "programming_completed_at": null,
  ...
}
You can supply a slug in the request body to give the survey a stable, human-readable identifier. If you omit it, Questra generates one from the name.
Reference: POST /surveys

Step 2 — Upload a questionnaire file

Upload the source document (Word doc, PDF, plain text questionnaire, etc.) that the agent will use to program the survey. This is a multipart/form-data request. Pass the survey ID in the survey_id field to attach the file to a survey.
curl -X POST https://api.questra.ai/files \
  -H "Authorization: Bearer $QUESTRA_API_KEY" \
  -F "file=@questionnaire.docx" \
  -F "survey_id=a1b2c3d4-e5f6-4890-abcd-ef1234567890"
The response is a File object. Save the id.
{
  "id": "b9c8d7e6-f5a4-4321-9876-543210fedcba",
  "org_id": "c0c1c2c3-d4d5-4678-9abc-def012345678",
  "survey_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
  "name": "questionnaire.docx",
  "mime_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  "created_at": "2025-04-01T10:00:00Z"
}
You can omit survey_id to upload a file without attaching it to a survey yet. Unattached files can be associated with a survey later when triggering AI programming.
Reference: POST /files

Step 3 — Trigger AI programming

Tell the agent to read the uploaded file and build out the survey. Pass the survey ID (or slug) and file ID in the URL. This kicks off an asynchronous job — the endpoint returns immediately; the work happens in the background.
curl -X POST https://api.questra.ai/surveys/a1b2c3d4-e5f6-4890-abcd-ef1234567890/program-from-file/b9c8d7e6-f5a4-4321-9876-543210fedcba \
  -H "Authorization: Bearer $QUESTRA_API_KEY"
Once programming begins, the survey’s locked field becomes true and programming_started_at is set. No other writes to the survey are accepted while it is locked. Reference: POST /surveys/{id}/program-from-file/{fileId}

Step 4 — Track programming progress

Register a webhook so Questra pushes a notification to your server the moment programming finishes. You only need to do this once per integration; the same webhook handles every future job.
curl -X POST https://api.questra.ai/webhooks \
  -H "Authorization: Bearer $QUESTRA_API_KEY" \
  -H "X-Org-Id: $QUESTRA_ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/questra",
    "events": ["programming.started", "programming.finished"],
    "description": "AI programming lifecycle"
  }'
When programming finishes, Questra sends a POST to your endpoint:
{
  "event": "programming.finished",
  "data": {
    "survey_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
    "file_id": "b9c8d7e6-f5a4-4321-9876-543210fedcba",
    "status": "completed"
  },
  "timestamp": "2025-04-01T10:02:34Z"
}
The status field is one of completed, failed, or cancelled. On completed, proceed to export. On failed, inspect the survey and retry.
Subscribe to block.created, page.created, and related survey-scope events to stream live progress into your UI as the agent works — not just the final result.
See the Webhooks guide for signature verification, retry behavior, and the full event catalog. References: POST /webhooks · Webhooks guide

Step 5 — Pay and export the survey

Once you receive a programming.finished event with status: "completed", pay for the survey and export it. Payment deducts one credit from your organization’s balance and is required before an export will succeed.
curl -X POST "https://api.questra.ai/surveys/a1b2c3d4-e5f6-4890-abcd-ef1234567890/pay" \
  -H "Authorization: Bearer $QUESTRA_API_KEY" \
  -H "X-Org-Id: $QUESTRA_ORG_ID"
A 204 No Content response confirms payment. The survey’s paid_at field is then set and the survey is ready to export. Use GET /export/formats to discover the available platform slugs.
curl https://api.questra.ai/export/formats \
  -H "Authorization: Bearer $QUESTRA_API_KEY"
Then export to your target platform:
# Decipher
curl "https://api.questra.ai/surveys/a1b2c3d4-e5f6-4890-abcd-ef1234567890/export/decipher" \
  -H "Authorization: Bearer $QUESTRA_API_KEY"

# Qualtrics
curl "https://api.questra.ai/surveys/a1b2c3d4-e5f6-4890-abcd-ef1234567890/export/qualtrics" \
  -H "Authorization: Bearer $QUESTRA_API_KEY"
The response body is the platform-native file (XML for Decipher, QSF JSON for Qualtrics). Write it to disk and import it into the target platform directly.
If your organization has no credits remaining, the pay endpoint returns 400. Contact us to top up your balance.
References: POST /surveys/{id}/pay · GET /surveys/{id}/export/{format}

Putting it all together

Set up the webhook once during onboarding, then kick off jobs and let Questra call your endpoint when they finish.
const API = "https://api.questra.ai";
const KEY = process.env.QUESTRA_API_KEY;
const ORG = process.env.QUESTRA_ORG_ID;
const headers = { Authorization: `Bearer ${KEY}`, "X-Org-Id": ORG };

// One-time setup: register your webhook endpoint
await fetch(`${API}/webhooks`, {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    url: "https://your-server.com/webhooks/questra",
    events: ["programming.finished"],
    description: "AI programming lifecycle",
  }),
});

// Per-job: create survey → upload file → trigger programming
const survey = await fetch(`${API}/surveys`, {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Q4 Customer Satisfaction Study" }),
}).then((r) => r.json());

const form = new FormData();
form.append("file", new Blob([fileBytes], { type: "application/pdf" }), "questionnaire.pdf");
form.append("survey_id", survey.id);
const file = await fetch(`${API}/files`, { method: "POST", headers, body: form }).then((r) => r.json());

await fetch(`${API}/surveys/${survey.id}/program-from-file/${file.id}`, { method: "POST", headers });

// Questra POSTs to your endpoint when done — handle it like this:
async function onProgrammingFinished({ survey_id, status }) {
  if (status !== "completed") return; // handle failure/cancellation as needed

  // Pay for the survey (deducts one credit)
  await fetch(`${API}/surveys/${survey_id}/pay`, { method: "POST", headers });

  // Export to your target platform
  const xml = await fetch(`${API}/surveys/${survey_id}/export/decipher`, { headers }).then((r) =>
    r.text()
  );
  // Write xml to disk or forward to Decipher's import API
}

Next steps