Create assessment

Developer documentation

The Assessly API, and the webhooks that call you back

Read submissions and assessments from your own system, and be told the moment a candidate finishes. Keys are scoped, requests are rate limited by plan, and every webhook is signed.

Paid plans only

API access is not available on the Free plan. Starter and above include it, with a request allowance that grows with the plan.

curl -H "Authorization: Bearer sk_live_..." \
  https://assessly.app/api/v1/submissions

Two halves

  • REST API

    Pull what the dashboard shows: every submission with its answers and scores, every assessment with its questions.

    • List and filter submissions
    • Read one submission in full
    • List and read assessments
    • Keys scoped per integration
  • Webhooks

    Be told when a submission is created, updated, completed or scored, instead of asking.

    • Four events
    • Retried on failure
    • HMAC-SHA256 signed
    • Filter by event when you register

Three steps to a first response

  1. Create a key

    In Settings, Integrations. It is shown once; copy it then.

    sk_live_<random_string>
  2. Make a request

    Send the key as a bearer token on any endpoint.

    GET /api/v1/submissions
    Authorization: Bearer sk_live_...
  3. Register a webhook

    Give us a URL and pick the events. We call you from then on.

    POST https://your-app.example/webhooks/assessly

Authentication

One header on every request. A key belongs to a company and carries the scopes it was created with.

Keys look like sk_live_<random_string>. Send yours in the Authorization header, as a bearer token.

JavaScript
const response = await fetch('https://assessly.app/api/v1/submissions?assessment_id=abc123&page=1&limit=50', {
method: 'GET',
headers: {
'Authorization': 'Bearer sk_live_abc123...',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);

Scopes

A new key starts with the first and nothing else. Add only what the integration will call.

API scopes
ScopeAllows
submissions.readRead submission data (default)
submissions.writeCreate and update submissions
assessments.readRead assessment data and configurations
assessments.writeCreate, update, and delete assessments
webhooks.readView webhook configurations
webhooks.writeCreate and manage webhooks

Rate limits

Requests per hour, per company. API access is not available on the Free plan.

Rate limits by plan
PlanRequests per hour
Starter500
Pro2,000
Business5,000
EnterpriseCustom

Reference

Open an endpoint for its parameters, a request in five languages, and the shape of the response.

Submissions

GET/api/v1/submissionsList all submissions for your company with optional filtering and pagination

Query parameters

Query parameters
Query parameterTypeWhat it does
assessment_idstringOnly submissions to this assessment
statusstringcompleted or pending
candidate_namestringPartial match on the candidate name
candidate_emailstringPartial match on the candidate email
min_scorenumberScore at or above this value
max_scorenumberScore at or below this value
submitted_afterstringSubmitted after this date (ISO 8601)
submitted_beforestringSubmitted before this date (ISO 8601)
has_ai_summarybooleanWhether an AI summary exists
sort_bystringsubmitted_at, score, time_spent_seconds or status
sort_orderstringasc or desc. Default: desc
pagenumberPage number. Default: 1
limitnumberItems per page. Default: 50, maximum: 100

Request

JavaScript
const response = await fetch('https://assessly.app/api/v1/submissions?assessment_id=abc123&status=completed&min_score=70&submitted_after=2024-01-01T00%3A00%3A00Z&has_ai_summary=true&sort_by=score&sort_order=desc&page=1&limit=50', {
method: 'GET',
headers: {
'Authorization': 'Bearer sk_live_abc123...',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);

Response

JSON
{
"data": [
{
"id": "sub_123",
"assessment_id": "abc123",
"candidate_name": "John Doe",
"candidate_email": "john@example.com",
"status": "completed",
"score": 85,
"submitted_at": "2024-11-24T10:30:00Z",
"created_at": "2024-11-24T10:00:00Z",
"custom_fields": {
"source": "linkedin",
"campaign_id": "summer2024",
"referrer": "john_doe"
}
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 150,
"total_pages": 3
}
}
GET/api/v1/submissions/:idRetrieve detailed information about a specific submission, including all responses and AI analysis

Path parameters

Path parameters
Path parameterTypeWhat it does
idstringThe submission id

Request

JavaScript
const response = await fetch('https://assessly.app/api/v1/submissions/sub_123', {
method: 'GET',
headers: {
'Authorization': 'Bearer sk_live_abc123...',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);

Response

JSON
{
"id": "sub_123",
"assessment_id": "abc123",
"assessment_title": "Frontend Developer Assessment",
"candidate_info": {
"name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890"
},
"responses": [
{
"question_id": "q_001",
"question_text": "Describe your experience with React",
"question_type": "text",
"answer": "I have 3 years of experience...",
"score": 90,
"ai_feedback": "Excellent answer demonstrating deep understanding."
}
],
"submitted_at": "2024-11-24T10:30:00Z",
"status": "completed",
"score": 85,
"grade": "A",
"ai_summary": {
"overall": "Strong candidate with excellent technical skills...",
"strengths": [
"Problem solving",
"Communication",
"Technical knowledge"
],
"concerns": []
},
"time_spent_seconds": 1800,
"created_at": "2024-11-24T10:00:00Z",
"custom_fields": {
"source": "linkedin",
"campaign_id": "summer2024",
"referrer": "john_doe",
"ats_candidate_id": "12345"
}
}

Custom fields from the link

Anything you append to an assessment link comes back on the submission. Track the source, the campaign, or the candidate id in your ATS.

Every query parameter on the link, other than the reserved ones, is stored in custom_fields when the candidate submits.

https://assessly.app/a/company/assessment?source=linkedin&campaign_id=summer2024&referrer=john_doe

Reserved

The link keeps these for itself; they are not stored.

  • token
  • assessmentId
  • assessment_id
  • companySlug
  • assessmentSlug
  • company-slug
  • assessment-slug

Common uses

Marketing attribution
?source=linkedin&campaign_id=123
ATS integration
?ats_candidate_id=12345
Referral tracking
?referrer=john_doe
Location
?location=sf&office=hq

In the response

JSON
{
"id": "sub_123",
"assessment_id": "abc123",
"candidate_name": "John Doe",
"custom_fields": {
"source": "linkedin",
"campaign_id": "summer2024",
"referrer": "john_doe",
"ats_candidate_id": "12345"
}
}

Assessments

GET/api/v1/assessmentsList all assessments for your company with optional filtering and pagination

Query parameters

Query parameters
Query parameterTypeWhat it does
statusstringactive, paused or archived
searchstringMatches the title and the description
is_publicbooleanWhether the assessment is public
created_afterstringCreated after this date (ISO 8601)
created_beforestringCreated before this date (ISO 8601)
has_time_limitbooleanWhether a time limit is set
has_max_submissionsbooleanWhether a submission cap is set
has_expiry_datebooleanWhether an expiry date is set
include_countsbooleanAdd submission counts to each row
sort_bystringcreated_at, updated_at, title or status
sort_orderstringasc or desc. Default: desc
pagenumberPage number. Default: 1
limitnumberItems per page. Default: 50, maximum: 100

Request

JavaScript
const response = await fetch('https://assessly.app/api/v1/assessments?status=active&search=frontend&is_public=true&has_time_limit=true&include_counts=true&sort_by=created_at&sort_order=desc&page=1&limit=50', {
method: 'GET',
headers: {
'Authorization': 'Bearer sk_live_abc123...',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);

Response

JSON
{
"data": [
{
"id": "abc123",
"title": "Frontend Developer Assessment",
"slug": "frontend-developer-assessment",
"description": "Assess frontend development skills",
"status": "active",
"is_public": true,
"created_at": "2024-11-01T10:00:00Z",
"updated_at": "2024-11-24T10:00:00Z",
"expires_at": null,
"time_limit_minutes": 60,
"max_submissions": 100,
"max_attempts": 2,
"show_timer": true,
"assessment_format": "standard",
"submission_count": 45
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 12,
"total_pages": 1
}
}
GET/api/v1/assessments/:idRetrieve detailed information about a specific assessment, including all questions and configuration

Path parameters

Path parameters
Path parameterTypeWhat it does
idstringThe assessment id

Request

JavaScript
const response = await fetch('https://assessly.app/api/v1/assessments/abc123', {
method: 'GET',
headers: {
'Authorization': 'Bearer sk_live_abc123...',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);

Response

JSON
{
"id": "abc123",
"title": "Frontend Developer Assessment",
"description": "Assess frontend development skills including React, TypeScript, and CSS",
"instructions": "Complete all questions within 60 minutes",
"status": "active",
"password_protected": false,
"time_limit_minutes": 60,
"personal_info_fields": [
"name",
"email",
"phone"
],
"questions": [
{
"id": "q_001",
"type": "text",
"question": "Describe your experience with React",
"description": "Please provide details about your React experience",
"required": true,
"order": 1,
"validation_rules": {
"min_length": 50,
"max_length": 1000
}
},
{
"id": "q_002",
"type": "multiple_choice",
"question": "Which React hooks have you used?",
"options": [
"useState",
"useEffect",
"useContext",
"useReducer"
],
"required": true,
"order": 2
}
],
"created_at": "2024-11-01T10:00:00Z",
"updated_at": "2024-11-24T10:00:00Z",
"total_submissions": 45
}
PATCH/api/v1/assessments/:idUpdate assessment details, questions, and configuration programmaticallyNot yet available

Not served yet. Update assessment details, questions, and configuration programmatically. It will appear here when it ships, and the change log will say so.

DELETE/api/v1/assessments/:idPermanently remove an assessment from your accountNot yet available

Not served yet. Permanently remove an assessment from your account. It will appear here when it ships, and the change log will say so.

Webhooks

Register a URL in Settings, Integrations, choose the events, and each one arrives as a POST with a signature to check.

Events

  • submission.created

    Triggered when a new submission is created

  • submission.updated

    Triggered when a submission is updated

  • submission.completed

    Triggered when a submission is completed

  • submission.scored

    Triggered when AI scoring is finished

Payload

JSON
{
"event": "submission.created",
"timestamp": "2024-11-24T10:30:00Z",
"data": {
"id": "sub_123",
"assessment_id": "abc123",
"candidate_name": "John Doe",
"candidate_email": "john@example.com",
"status": "completed",
"submitted_at": "2024-11-24T10:30:00Z"
}
}

Verify the signature

Every delivery carries X-Webhook-Signature, an HMAC-SHA256 of the raw body with your webhook secret. Compare it in constant time before you trust the event.

JavaScript
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.raw({ type: 'application/json' }));
function verifyWebhookSignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const digest = hmac.update(payload).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(digest)
);
}
app.post('/webhooks/assessly', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const secret = process.env.WEBHOOK_SECRET;
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body.toString());
switch (event.event) {
case 'submission.created':
console.log('New submission:', event.data);
break;
case 'submission.completed':
console.log('Submission completed:', event.data);
break;
}
res.status(200).send('OK');
});
app.listen(3000);

Ready to build?

Create a key on any paid plan and the first request works in a minute. If something in this reference is wrong or missing, say so.

All documentation