REST API¶
Askalot provides a REST API for programmatic access to all platform capabilities. The API enables you to automate survey workflows, integrate with external systems, and build custom applications on top of the Askalot platform.
Getting Started¶
Base URL¶
Replace <tenant> with your organization's tenant name (e.g., portor.acme.askalot.io).
Authentication¶
All API requests require authentication using a personal API token.
Generating a Token:
- Log in to
https://roundtable.<tenant>.askalot.io - Navigate to Profile Settings
- Scroll to "API Tokens" section
- Click "Generate New Token"
- Copy the token (shown only once)
Keep Your Token Secure
API tokens provide full access to your account. Never share tokens or commit them to version control.
Using Your Token:
Include your token in the X-Api-Token header:
Token Properties:
- Format: tokens are
aslat_-prefixed (aslat_<32 characters>); the prefix lets secret scanners flag an accidental commit. Only the prefix is stored server-side for identification — the full token is shown once at creation and never again. - Header: send it in the
X-Api-Tokenheader. This is the REST credential path; theAuthorization: Bearerheader on Portor is reserved for OAuth access tokens (the MCP transport), not API tokens. - Scope: authenticates the REST API only. It inherits your account's roles and permissions and is not accepted by the MCP interface.
- Revocation: revoke at any time from Profile Settings; revocation takes effect immediately.
Token Lifetime & Rotation¶
You choose an expiry when creating a token — the Profile Settings dropdown offers 30 days, 90 days (the default), or 1 year. An expiry is required; there is no "never-expiring" option for new tokens (only older, grandfathered tokens may lack one).
- Profile Settings shows each token's expiry date and days remaining, and flags a token for rotation once it is within 14 days of expiring.
- To rotate: create a replacement token, update your integration to use it, then revoke the old one. Tokens do not auto-renew — an expired token returns
401and must be replaced.
REST vs MCP authentication
This API token is for REST clients (integrations, CI, server-to-server, and sandboxed code that drives the REST API for bulk work). The MCP interface does not accept it — MCP authenticates through an interactive OAuth 2.1 browser flow and resolves OAuth (or synthetic service) bearer tokens exclusively. Use the API token for non-interactive REST; use the OAuth sign-in for MCP clients like the Askalot plugin.
Interactive Documentation¶
Explore the API interactively using Swagger UI at:
The interactive documentation lets you:
- Browse all available endpoints
- View request/response schemas
- Test API calls directly in your browser
Core Resources¶
Project Ownership
Projects, Bundles, Questionnaires, Campaigns, Respondents, Surveys, Respondent Pools, and Sampling Strategies are scoped to the Project that owns them: creating, updating, or deleting one of these resources requires you to be an owner of its parent Project (or an organization administrator). See Entity Isolation & Sharing for the full ownership model.
Projects¶
Projects are top-level containers for organizing your survey research.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/projects |
List all projects |
POST |
/api/v1/projects |
Create a new project |
GET |
/api/v1/projects/{id} |
Get project by ID |
PUT |
/api/v1/projects/{id} |
Update a project |
DELETE |
/api/v1/projects/{id} |
Delete a project |
POST |
/api/v1/projects/{id}/owners |
Add project owners |
DELETE |
/api/v1/projects/{id}/owners |
Remove project owners |
Owners stay within the organization
Owner targets must belong to the project's organization. A request naming a user from another organization is rejected with 403 ({"error": "forbidden"}) and no partial write occurs — either every listed user is applied or none is.
Example: Create a Project
curl -X POST https://portor.<tenant>.askalot.io/api/v1/projects \
-H "X-Api-Token: your_token" \
-H "Content-Type: application/json" \
-d '{
"name": "Customer Satisfaction Study",
"description": "Q1 2026 customer feedback survey"
}'
Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Customer Satisfaction Study",
"description": "Q1 2026 customer feedback survey",
"created_at": "2026-01-02T10:30:00Z",
"questionnaire_ids": [],
"campaign_ids": []
}
Questionnaires¶
Questionnaires define survey structure using QML (Questionnaire Markup Language).
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/questionnaires |
List all questionnaires |
POST |
/api/v1/questionnaires |
Create a new questionnaire |
GET |
/api/v1/questionnaires/{id} |
Get questionnaire by ID |
PUT |
/api/v1/questionnaires/{id} |
Update a questionnaire |
DELETE |
/api/v1/questionnaires/{id} |
Delete a questionnaire |
Query Parameters:
project_id- Filter by projectstatus- Filter by status (draft,active,paused,completed)
Campaigns¶
Campaigns link questionnaires to respondents for data collection.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/campaigns |
List all campaigns |
POST |
/api/v1/campaigns |
Create a new campaign |
GET |
/api/v1/campaigns/{id} |
Get campaign by ID |
PUT |
/api/v1/campaigns/{id} |
Update a campaign |
DELETE |
/api/v1/campaigns/{id} |
Delete a campaign |
PUT |
/api/v1/campaigns/{id}/pool |
Assign a respondent pool to the campaign |
GET |
/api/v1/campaigns/{id}/pool |
Get the campaign's assigned pool |
PUT |
/api/v1/campaigns/{id}/questionnaire |
Change the campaign's questionnaire |
POST |
/api/v1/campaigns/{id}/interviewers |
Add interviewers to the campaign |
DELETE |
/api/v1/campaigns/{id}/interviewers |
Remove interviewers from the campaign |
Query Parameters:
project_id- Filter by projectquestionnaire_id- Filter by questionnairestatus- Filter by statusexpand=true- Include related objects (project, questionnaire, respondents)
Campaign Modes:
Askalot supports two campaign modes:
- Direct Mode: Respondents complete surveys independently via unique links
- Interviewer-Assisted Mode: Interviewers administer surveys to respondents by phone or in person
The mode is determined automatically based on whether interviewers are assigned to the campaign.
Respondents¶
Respondents are the survey targets—the people being surveyed.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/respondents |
List all respondents |
POST |
/api/v1/respondents |
Create a new respondent |
GET |
/api/v1/respondents/{id} |
Get respondent by ID |
PUT |
/api/v1/respondents/{id} |
Update a respondent |
DELETE |
/api/v1/respondents/{id} |
Delete a respondent |
Query Parameters:
source_system- Filter by source (e.g.,crm,panel_provider)campaign_id- Filter by campaign
Note: Interviewers are Users with the interviewer role. They are managed through the /api/v1/users endpoint.
Example: Import a Respondent
curl -X POST https://portor.<tenant>.askalot.io/api/v1/respondents \
-H "X-Api-Token: your_token" \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Smith",
"email": "[email protected]",
"age": 35,
"gender": "female",
"location": "New York",
"external_id": "CRM-12345",
"source_system": "salesforce",
"custom_attributes": {"state": "NY", "zip_code": "10001"}
}'
Demographics Fields:
Respondents support demographic data for targeting and analysis:
age- Respondent agegender- Gender identitylocation- Geographic location (free text, typically"City, Country")external_id- Your system's identifiersource_system- Source of the respondent data
Geography finer than location — street, city, state, postal code, country as separate values — goes in custom_attributes, and is addressable as custom_attributes.<key> wherever a respondent attribute can be named (sampling factors, external-input mappings, calibration targets). Extra top-level keys are ignored rather than stored, so a CRM export that sends city as its own field loses that value silently.
Surveys¶
Surveys track individual survey sessions and responses.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/surveys |
List all surveys |
POST |
/api/v1/surveys |
Create a new survey session |
GET |
/api/v1/surveys/{id} |
Get survey by ID |
PUT |
/api/v1/surveys/{id} |
Update survey status |
DELETE |
/api/v1/surveys/{id} |
Delete a survey |
GET |
/api/v1/surveys/{id}/current-step |
Current question + options + status (Survey Presentation Format) |
POST |
/api/v1/surveys/{id}/responses |
Submit an answer; the next step is folded into the response |
POST |
/api/v1/surveys/{id}/finish |
Explicitly complete the survey (idempotent) |
Query Parameters:
questionnaire_id- Filter by questionnairecampaign_id- Filter by campaignrespondent_id- Filter by respondentstatus- Filter by status (pending,in_progress,completed)expand=true- Include related objects
Survey responses carry status plus a derived, read-only completed boolean (true exactly when status is completed). Completion is expressed by updating status — there is no completed input or filter parameter.
Answering a survey through POST /api/v1/surveys/{id}/responses is limited to testing and simulation: a survey that has had an invitation sent is refused, so a live fielded interview cannot be written to programmatically. Answers submitted this way are recorded as machine-produced and their response times are excluded from speeder detection.
Respondent Pools¶
Respondent pools organize respondents into reusable groups for campaigns.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/respondent-pools |
List all respondent pools |
POST |
/api/v1/respondent-pools |
Create a new pool |
GET |
/api/v1/respondent-pools/{id} |
Get pool by ID |
PUT |
/api/v1/respondent-pools/{id} |
Update a pool |
DELETE |
/api/v1/respondent-pools/{id} |
Delete a pool |
POST |
/api/v1/respondent-pools/{id}/respondents |
Add respondents to pool |
DELETE |
/api/v1/respondent-pools/{id}/respondents |
Remove respondents from pool |
Query Parameters:
project_id- Filter by project
Sampling Strategies¶
Sampling strategies define target demographic distributions for creating representative samples.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/sampling-strategies |
List all strategies |
POST |
/api/v1/sampling-strategies |
Create a new strategy |
GET |
/api/v1/sampling-strategies/{id} |
Get strategy by ID |
PUT |
/api/v1/sampling-strategies/{id} |
Update a strategy |
POST |
/api/v1/sampling-strategies/{id}/advance |
Advance a strategy: clone it and append reality-grounded outcome factors (the original is never edited) |
DELETE |
/api/v1/sampling-strategies/{id} |
Delete a strategy |
Query Parameters:
project_id- Filter by project
Example: Create a Sampling Strategy
curl -X POST https://portor.<tenant>.askalot.io/api/v1/sampling-strategies \
-H "X-Api-Token: your_token" \
-H "Content-Type: application/json" \
-d '{
"name": "Representative Sample",
"project_id": "550e8400-e29b-41d4-a716-446655440000",
"target_size": 100,
"factors": [
{
"name": "Gender",
"attribute_path": "gender",
"target_distribution": {"male": 0.48, "female": 0.50, "other": 0.02}
},
{
"name": "Age Group",
"attribute_path": "age",
"factor_type": "bucketed",
"target_distribution": {"18-34": 0.35, "35-54": 0.40, "55+": 0.25}
}
]
}'
Bundles¶
A Bundle binds one Project → one Questionnaire → a selected Campaign subset to a single linear Bronze → Silver → Gold dataset chain — the unit the Balansor pipeline board organizes around.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/bundles |
List all bundles |
POST |
/api/v1/bundles |
Create a new bundle |
GET |
/api/v1/bundles/{id} |
Bundle detail, including its stage recipes (Silver Calibration Targets, Gold operations) |
DELETE |
/api/v1/bundles/{id} |
Delete a bundle (and its dataset chain) |
POST |
/api/v1/bundles/{id}/clone |
Clone a bundle — deep copy: ready datasets and stage recipes carry over |
GET |
/api/v1/bundles/{id}/quality |
The bundle's Representativeness story (selection, fielded, weighted, fielding shift, response) |
GET |
/api/v1/bundles/{id}/comparison |
Bronze-vs-Silver comparison (409 conflict until both stages are ready) |
GET |
/api/v1/bundles/{id}/coding |
Open-end coding state — the coding candidates the Bronze offers, the analyst's question selection, and per question the proposed dimensions with their categories and selection state (deciding which dimensions become columns stays in the Balansor UI) |
PUT |
/api/v1/bundles/{id}/coding/selection |
Set which open-text units this bundle codes — full replacement set of unit keys (manager role, identified user required) |
POST |
/api/v1/bundles/{id}/strategy |
Assign or clear the bundle's current sampling strategy (manager role, identified user required) |
PUT |
/api/v1/bundles/{id}/targets |
Edit the bundle's Calibration Targets — the Silver weighting spec (manager role, identified user required) |
Query Parameters:
project_id- Filter by project
Stage operations (extraction, coding + weighting, refinement, export) are driven through the MCP Bundle/Dataset tools — see mcp-bundle-tools.md and mcp-dataset-tools.md for the full quality and stage parameter reference.
Response Format¶
Success Responses¶
All successful responses return JSON with standard HTTP status codes:
| Status | Meaning |
|---|---|
200 OK |
Request succeeded (including resource creation — the created object is returned in the body) |
204 No Content |
Resource deleted successfully |
Error Responses¶
Errors use a single envelope: a machine-readable error code plus a human-readable detail:
| Status | error code |
Meaning |
|---|---|---|
401 |
unauthenticated |
Missing, invalid, or expired credentials |
403 |
forbidden |
You lack the capability, or you don't own the target resource |
404 |
not_found |
The resource does not exist (or is not visible to you) |
409 |
conflict |
Valid request, but the resource isn't ready yet (e.g. comparing a bundle whose chain is still processing) — retry after the pipeline finishes |
422 |
validation_error |
Malformed input; detail carries a list of field-level issues |
OpenAPI Specification¶
The complete API specification is available in OpenAPI 3.0 format:
Use this specification to generate typed API clients in your preferred programming language.
Common Workflows¶
Automating Campaign Creation¶
# 1. Create a project
PROJECT_ID=$(curl -s -X POST https://portor.<tenant>.askalot.io/api/v1/projects \
-H "X-Api-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Q1 Survey"}' | jq -r '.id')
# 2. Create a questionnaire (the QML itself is the payload — there is no file to stage)
QUESTIONNAIRE_ID=$(curl -s -X POST https://portor.<tenant>.askalot.io/api/v1/questionnaires \
-H "X-Api-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg p "$PROJECT_ID" --rawfile c feedback.qml \
'{name: "Customer Feedback", project_id: $p, content: $c}')" | jq -r '.id')
# 3. Create a campaign
curl -X POST https://portor.<tenant>.askalot.io/api/v1/campaigns \
-H "X-Api-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\": \"Q1 Campaign\", \"project_id\": \"$PROJECT_ID\", \"questionnaire_id\": \"$QUESTIONNAIRE_ID\"}"
Importing Respondents from Your CRM¶
# Import respondents from a CSV or your CRM system
for respondent in "${RESPONDENTS[@]}"; do
curl -X POST https://portor.<tenant>.askalot.io/api/v1/respondents \
-H "X-Api-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d "$respondent"
done
Next Steps¶
-
MCP Interface
Enable AI agents to interact with the platform
-
Creating Surveys
Learn QML syntax for questionnaire design
-
Campaign Management
Configure and launch survey campaigns