Questionnaire Responses
Questionnaire Responses represent filled-out DDQ questionnaires. They are the core workflow object in KYC Genie, linking a questionnaire template to a specific entity being assessed.
Response Object
The response object contains all metadata about a filled questionnaire.
Attributes
| Field | Type | Description |
|---|---|---|
id | uuid | Unique identifier |
response_name | string | Display name for the response |
questionnaire | object | Nested questionnaire template object (id, name, type) |
subject_entity | object | Nested entity being assessed (id, name, type) |
status | string | Current workflow status (see Status Values below) |
sent_by | string | Identifier of the user who sent the response |
responded_by | object | Profile object of user who submitted the response (null until submitted) |
reviewed_by | object | Profile object of analyst who reviewed the response (null until reviewed) |
manager_reviewer | object | Profile object of manager who performed final review (null until manager review) |
created_at | datetime | Creation timestamp |
submitted_at | datetime | Timestamp of submission (null if not submitted) |
reviewed_at | datetime | Timestamp of analyst review (null if not reviewed) |
manager_reviewed_at | datetime | Timestamp of manager review (null if not manager reviewed) |
updated_at | datetime | Last update timestamp |
answers | array | Array of answer objects (only in detail view) |
Status Values
Responses follow a state machine with specific allowed transitions:
| Status | Description |
|---|---|
PREFILL | You're filling the form before sending to entity (no email sent) |
DRAFT | Form sent to entity for completion (email sent on creation) |
SUBMITTED | Entity has submitted the completed form for your review |
UNDER_REVIEW | Your team is currently reviewing the submission |
CHANGES_REQUESTED | Your team requested changes (returns to DRAFT status) |
REVIEWED | Initial review complete, awaiting final approval |
APPROVED | Final approval granted |
REJECTED | Response rejected |
RESUBMITTED | Entity resubmitted after changes were requested |
Create Questionnaire Response
Creates a new DDQ response linking a questionnaire template to an entity. The response can only be created in DRAFT or PREFILL status.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
questionnaire_id | integer (v1) / uuid (v2) | Yes | ID of the DDQ template. v1 accepts integer PK; v2 accepts UUID (integer still works for backwards compatibility) |
response_name | string | No | Custom name for this response (auto-generated if not provided) |
status | string | No | Initial status (default: PREFILL). Allowed values: PREFILL or DRAFT |
webhook_url | string | No | Override webhook URL for events on this response only |
New responses can only be created with status DRAFT or PREFILL. Any other status value returns a 400 error. All other statuses are set through workflow transitions.
Creating a response with status: "DRAFT" sends an onboarding email to the entity. The linked entity must already have an email address stored - the API will return 400 if the entity has no email.
Create Response
curl -X POST https://api.kycgenie.com/api/v1/responses/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: resp-acme-ddq-20260114" \
-d '{
"questionnaire_id": 55,
"entity": {
"entity_id": "1b83f447-4a1e-4589-8a12-b8767e3c5426"
},
"status": "PREFILL"
}'
curl -X POST https://api.kycgenie.com/api/v2/questionnaire-responses/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: resp-acme-ddq-20260114" \
-d '{
"questionnaire_id": "7f1a3c4e-5b9d-4e82-a1f3-8d2c5e7b9f0a",
"entity_id": "1b83f447-4a1e-4589-8a12-b8767e3c5426",
"status": "PREFILL"
}'
import requests
resp = requests.post(
"https://api.kycgenie.com/api/v1/responses/",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Idempotency-Key": "resp-acme-ddq-20260114",
},
json={
"questionnaire_id": 55,
"entity": {"entity_id": "1b83f447-4a1e-4589-8a12-b8767e3c5426"},
"status": "PREFILL",
},
)
data = resp.json()
print(f"Created response: {data['id']}")
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
response = client.questionnaire_responses.create(
questionnaire_id="7f1a3c4e-5b9d-4e82-a1f3-8d2c5e7b9f0a",
entity_id="1b83f447-4a1e-4589-8a12-b8767e3c5426",
status="PREFILL",
)
print(f"Created: {response.id}") # UUID
print(f"Status: {response.status}") # PREFILL
const resp = await fetch('https://api.kycgenie.com/api/v1/responses/', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'Idempotency-Key': 'resp-acme-ddq-20260114',
},
body: JSON.stringify({
questionnaire_id: 55,
entity: { entity_id: '1b83f447-4a1e-4589-8a12-b8767e3c5426' },
status: 'PREFILL',
}),
});
const data = await resp.json();
console.log(`Created: ${data.id}`);
const resp = await fetch('https://api.kycgenie.com/api/v2/questionnaire-responses/', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'Idempotency-Key': 'resp-acme-ddq-20260114',
},
body: JSON.stringify({
questionnaire_id: '7f1a3c4e-5b9d-4e82-a1f3-8d2c5e7b9f0a',
entity_id: '1b83f447-4a1e-4589-8a12-b8767e3c5426',
status: 'PREFILL',
}),
});
const data = await resp.json();
console.log(`Created: ${data.id}`);
Response (201 Created)
{
"id": "d67177bf-6871-45bf-85f2-95a1e52ccc64",
"response_name": "Acme Corp - DDQ - Template",
"questionnaire": {"id": "7f1a3c4e-5b9d-4e82-a1f3-8d2c5e7b9f0a", "name": "DDQ - Template", "questionnaire_type": "ddq"},
"subject_entity": {
"id": "1b83f447-4a1e-4589-8a12-b8767e3c5426",
"name": "Acme Corp",
"entity_type": "company"
},
"status": "PREFILL",
"created_at": "2026-01-14T12:41:56.456529Z",
"submitted_at": null,
"reviewed_at": null,
"manager_reviewed_at": null,
"updated_at": "2026-01-14T12:41:56.456555Z"
}Batch Create Responses
Create multiple responses in a single request. Each item follows the same schema as the single create endpoint. Useful for bulk onboarding workflows.
Request Body
Array of response objects, each following the same schema as Create Response.
Batch Create Responses
curl -X POST https://api.kycgenie.com/api/v1/responses/batch/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: batch-onboard-20260114" \
-d '[
{
"questionnaire_id": 55,
"entity": {"entity_id": "1b83f447-4a1e-4589-8a12-b8767e3c5426"},
"status": "PREFILL"
},
{
"questionnaire_id": 55,
"entity": {"entity_id": "2c94g558-5b2f-5693-9b23-c9878f774437"},
"status": "PREFILL"
}
]'
curl -X POST https://api.kycgenie.com/api/v2/questionnaire-responses/batch/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: batch-onboard-20260114" \
-d '{
"responses": [
{
"questionnaire_id": "7f1a3c4e-5b9d-4e82-a1f3-8d2c5e7b9f0a",
"entity_id": "1b83f447-4a1e-4589-8a12-b8767e3c5426",
"status": "PREFILL"
},
{
"questionnaire_id": "7f1a3c4e-5b9d-4e82-a1f3-8d2c5e7b9f0a",
"entity_id": "2c94g558-5b2f-5693-9b23-c9878f774437",
"status": "PREFILL"
}
]
}'
import requests
# v1: bare list payload with nested entity object
resp = requests.post(
"https://api.kycgenie.com/api/v1/responses/batch/",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Idempotency-Key": "batch-onboard-20260114",
},
json=[
{
"questionnaire_id": 55,
"entity": {"entity_id": "1b83f447-4a1e-4589-8a12-b8767e3c5426"},
"status": "PREFILL",
},
{
"questionnaire_id": 55,
"entity": {"entity_id": "2c94g558-5b2f-5693-9b23-c9878f774437"},
"status": "PREFILL",
},
],
)
data = resp.json()
print(f"Created: {data['created_count']}, Failed: {data['failed_count']}")
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
result = client.questionnaire_responses.batch_create(
responses=[
{
"questionnaire_id": "7f1a3c4e-5b9d-4e82-a1f3-8d2c5e7b9f0a",
"entity_id": "1b83f447-4a1e-4589-8a12-b8767e3c5426",
"status": "PREFILL",
},
{
"questionnaire_id": "7f1a3c4e-5b9d-4e82-a1f3-8d2c5e7b9f0a",
"entity_id": "2c94g558-5b2f-5693-9b23-c9878f774437",
"status": "PREFILL",
},
]
)
print(f"Created: {result.created_count}, Failed: {result.failed_count}")
const resp = await fetch('https://api.kycgenie.com/api/v1/responses/batch/', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'Idempotency-Key': 'batch-onboard-20260114',
},
body: JSON.stringify([
{ questionnaire_id: 55, entity: { entity_id: '1b83f447-4a1e-4589-8a12-b8767e3c5426' }, status: 'PREFILL' },
{ questionnaire_id: 55, entity: { entity_id: '2c94g558-5b2f-5693-9b23-c9878f774437' }, status: 'PREFILL' },
]),
});
const data = await resp.json();
console.log(`Created: ${data.created_count}, Failed: ${data.failed_count}`);
const resp = await fetch('https://api.kycgenie.com/api/v2/questionnaire-responses/batch/', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'Idempotency-Key': 'batch-onboard-20260114',
},
body: JSON.stringify({
responses: [
{ questionnaire_id: '7f1a3c4e-5b9d-4e82-a1f3-8d2c5e7b9f0a', entity_id: '1b83f447-4a1e-4589-8a12-b8767e3c5426', status: 'PREFILL' },
{ questionnaire_id: '7f1a3c4e-5b9d-4e82-a1f3-8d2c5e7b9f0a', entity_id: '2c94g558-5b2f-5693-9b23-c9878f774437', status: 'PREFILL' },
],
}),
});
const data = await resp.json();
console.log(`Created: ${data.created_count}, Failed: ${data.failed_count}`);
Response (200 OK)
{
"created_count": 2,
"failed_count": 0,
"results": [
{"success": true, "response_id": "d67177bf-6871-45bf-85f2-95a1e52ccc64", "entity_id": "1b83f447-4a1e-4589-8a12-b8767e3c5426", "entity_name": "Acme Corp"},
{"success": true, "response_id": "e78288c0-7982-56c0-96g3-06b399885575", "entity_id": "2c94g558-5b2f-5693-9b23-c9878f774437", "entity_name": "Globex Ltd"}
]
}List Questionnaire Responses
Retrieves a paginated list of all responses in your tenant, ordered by creation date (newest first).
Query Parameters
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by status: DRAFT, PREFILL, SUBMITTED, etc. |
entity_id | uuid | Filter by subject entity UUID |
questionnaire_id | integer / uuid | Filter by questionnaire (integer PK in v1; UUID in v2) |
created_after | ISO 8601 | Return responses created after this datetime |
created_before | ISO 8601 | Return responses created before this datetime |
submitted_after | ISO 8601 | Return responses submitted after this datetime |
List Responses
curl "https://api.kycgenie.com/api/v1/responses/?status=SUBMITTED" \
-H "Authorization: Bearer YOUR_API_KEY"
curl "https://api.kycgenie.com/api/v2/questionnaire-responses/?status=SUBMITTED" \
-H "Authorization: Bearer YOUR_API_KEY"
import requests
resp = requests.get(
"https://api.kycgenie.com/api/v1/responses/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={"status": "SUBMITTED"},
)
data = resp.json()
for r in data["results"]:
print(f"{r['id']} {r['status']} {r['response_name']}")
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
page = client.questionnaire_responses.list(status="SUBMITTED")
for r in page.results:
print(f"{r.id} {r.status} {r.response_name}")
# Next page
if page.has_more:
page2 = client.questionnaire_responses.list(
status="SUBMITTED",
cursor=page.next_cursor,
)
const resp = await fetch(
'https://api.kycgenie.com/api/v1/responses/?status=SUBMITTED',
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const data = await resp.json();
data.results.forEach(r => console.log(`${r.id} ${r.status} ${r.response_name}`));
const resp = await fetch(
'https://api.kycgenie.com/api/v2/questionnaire-responses/?status=SUBMITTED',
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const data = await resp.json();
data.results.forEach(r => console.log(`${r.id} ${r.status} ${r.response_name}`));
Response
{
"count": 42,
"next": "https://api.kycgenie.com/api/v1/responses/?page=2",
"previous": null,
"results": [
{
"id": "d67177bf-6871-45bf-85f2-95a1e52ccc64",
"response_name": "Acme Corp - DDQ",
"questionnaire_name": "DDQ - Template",
"entity_name": "Acme Corp",
"status": "SUBMITTED",
"created_at": "2026-01-21T10:30:00Z",
"submitted_at": "2026-01-21T14:23:45Z"
}
]
}
Get Response
Retrieves a specific response by ID, including full metadata. The detail shape includes nested questionnaire and subject_entity objects with IDs, unlike the compact list shape.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
response_id | uuid | The unique identifier of the response |
Get Response
curl https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/ \
-H "Authorization: Bearer YOUR_API_KEY"
curl https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/ \
-H "Authorization: Bearer YOUR_API_KEY"
import requests
response_id = "d67177bf-6871-45bf-85f2-95a1e52ccc64"
resp = requests.get(
f"https://api.kycgenie.com/api/v2/questionnaire-responses/{response_id}/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = resp.json()
print(f"Status: {data['status']}")
print(f"Questionnaire: {data['questionnaire']['id']}")
print(f"Entity: {data['subject_entity']['name']}")
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
response = client.questionnaire_responses.get_details(
response_id="d67177bf-6871-45bf-85f2-95a1e52ccc64",
)
print(f"Status: {response.status}")
print(f"Entity: {response.subject_entity.name}")
const responseId = 'd67177bf-6871-45bf-85f2-95a1e52ccc64';
const resp = await fetch(
`https://api.kycgenie.com/api/v1/responses/${responseId}/`,
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const data = await resp.json();
console.log(`Status: ${data.status}`);
console.log(`Entity: ${data.subject_entity.name}`);
const responseId = 'd67177bf-6871-45bf-85f2-95a1e52ccc64';
const resp = await fetch(
`https://api.kycgenie.com/api/v2/questionnaire-responses/${responseId}/`,
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const data = await resp.json();
console.log(`Status: ${data.status}`);
console.log(`Questionnaire: ${data.questionnaire.id}`);
console.log(`Entity: ${data.subject_entity.name}`);
Response Example
{
"id": "d67177bf-6871-45bf-85f2-95a1e52ccc64",
"response_name": "Acme Corp - DDQ - Template",
"questionnaire": {"id": "7f1a3c4e-5b9d-4e82-a1f3-8d2c5e7b9f0a", "name": "DDQ - Template", "questionnaire_type": "ddq"},
"subject_entity": {
"id": "1b83f447-4a1e-4589-8a12-b8767e3c5426",
"name": "Acme Corp",
"entity_type": "company"
},
"status": "SUBMITTED",
"created_at": "2026-01-21T10:30:00Z",
"submitted_at": "2026-01-21T14:23:45Z",
"reviewed_at": null,
"manager_reviewed_at": null,
"updated_at": "2026-01-21T14:23:45Z"
}Get Questions
Returns every question in the questionnaire merged with its current answer state, grouped by section. This is the primary endpoint for PREFILL workflows - one call gives you every question_id needed to call PUT answers/, and clearly shows which required questions still need an answer.
This endpoint is only available on v2. On v1, retrieve questions from GET /api/v1/questionnaires/{id}/ and answers separately from the response detail.
Response Fields
| Field | Type | Description |
|---|---|---|
summary | object | Same completion counts as GET answers/ |
sections[].name | string | Section name (e.g. "Company Information") |
sections[].sub_sections[].name | string | null | Sub-section name, or null if not grouped |
questions[].question_id | integer | ID to pass to PUT answers/ |
questions[].question_type | string | Determines which answer field to populate - see Answer Formats |
questions[].options | array | null | Valid values for select / multi_select questions |
questions[].is_answered | boolean | true if a non-empty answer exists |
questions[].answer_text | string | null | Current answer, or null if unanswered |
file_questions[].file_question_id | integer | ID to pass to PUT answers/{fq_id}/file/ |
file_questions[].is_satisfied | boolean | true if files_uploaded ≥ min_files |
Get Questions
curl https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/questions/ \
-H "Authorization: Bearer YOUR_API_KEY"
# v2 only - not available on v1
# Use GET /api/v1/questionnaires/{id}/ for questions,
# and GET /api/v1/responses/{id}/ for answers.
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
form = client.questionnaire_responses.get_questions(
response_id="d67177bf-6871-45bf-85f2-95a1e52ccc64",
)
print(f"Progress: {form.summary.answered}/{form.summary.total_questions}")
print(f"Required missing: {form.summary.required_unanswered} text, {form.summary.required_files_unanswered} files")
# Build answers for all unanswered required questions
answers = []
for section in form.sections:
for sub in section.sub_sections:
for q in sub.questions:
if not q.is_answered and q.required:
answers.append({
"question_id": q.question_id,
"answer_text": f"Answer to: {q.question_text}",
})
client.questionnaire_responses.update_answers(
response_id="d67177bf-6871-45bf-85f2-95a1e52ccc64",
answers=answers,
)
import requests
response_id = "d67177bf-6871-45bf-85f2-95a1e52ccc64"
resp = requests.get(
f"https://api.kycgenie.com/api/v2/questionnaire-responses/{response_id}/questions/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
form = resp.json()
print(f"Progress: {form['summary']['answered']}/{form['summary']['total_questions']}")
for section in form["sections"]:
for sub in section["sub_sections"]:
for q in sub["questions"]:
status = "✓" if q["is_answered"] else ("✗ required" if q["required"] else "–")
print(f" [{status}] Q{q['question_number']}: {q['question_text'][:60]}")
const responseId = 'd67177bf-6871-45bf-85f2-95a1e52ccc64';
const resp = await fetch(
`https://api.kycgenie.com/api/v2/questionnaire-responses/${responseId}/questions/`,
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const { summary, sections } = await resp.json();
console.log(`Progress: ${summary.answered}/${summary.total_questions}`);
console.log(`Required missing: ${summary.required_unanswered} text, ${summary.required_files_unanswered} files`);
// Collect unanswered required questions
const missing = sections
.flatMap(s => s.sub_sections)
.flatMap(sub => sub.questions)
.filter(q => !q.is_answered && q.required);
console.log(`Unanswered required questions:`, missing.map(q => q.question_id));
// v2 only - not available on v1
Response Example
{
"summary": {
"total_questions": 83,
"answered": 45,
"required_unanswered": 12,
"total_file_questions": 5,
"files_uploaded": 3,
"required_files_unanswered": 2
},
"sections": [
{
"name": "Security",
"sub_sections": [
{
"name": "Data Protection",
"questions": [
{
"question_id": 42,
"question_text": "Describe your encryption at rest.",
"question_number": 1,
"question_type": "text",
"required": true,
"options": null,
"exclude_from_autofill": false,
"answer_text": "AES-256 encryption.",
"date_answer": null,
"is_answered": true,
"is_flagged": false
},
{
"question_id": 43,
"question_text": "Do you conduct annual penetration tests?",
"question_number": 2,
"question_type": "boolean",
"required": true,
"options": null,
"exclude_from_autofill": false,
"answer_text": null,
"date_answer": null,
"is_answered": false,
"is_flagged": false
}
],
"file_questions": [
{
"file_question_id": 67,
"description": "Upload latest penetration test report",
"required": true,
"min_files": 1,
"max_files": 1,
"accepted_extensions": ["pdf"],
"files_uploaded": 0,
"is_satisfied": false,
"attachments": []
}
]
}
]
}
]
}Get Response Answers
Retrieve all submitted answers for a response, including a completion summary, text answers, and file answers. Text and file answers are returned in separate arrays since they have different fields and different save paths.
Answer Object Fields
| Field | Type | Description |
|---|---|---|
id | uuid | Unique answer identifier |
question_id | integer | ID of the question being answered |
question_text | string | Full text of the question |
question_number | integer | Question number within questionnaire |
question_type | string | One of: text, email, tel, url, number, percentage, currency, boolean, yes_no_na, select, multi_select, country, date, text_and_date, address, structured. See Answer Formats. |
section | string | Section name (e.g., "Company Information") |
sub_section | string | Sub-section name (if applicable) |
required | boolean | Whether the question is required |
answer_text | string | The text answer provided |
date_answer | date | Date answer for date-type questions |
is_flagged | boolean | Whether this answer has been flagged for review |
comments | array | Array of comment objects on this answer |
num_of_comments | integer | Count of comments |
Get Response Answers
curl https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/answers/ \
-H "Authorization: Bearer YOUR_API_KEY"
curl https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/answers/ \
-H "Authorization: Bearer YOUR_API_KEY"
import requests
response_id = "d67177bf-6871-45bf-85f2-95a1e52ccc64"
resp = requests.get(
f"https://api.kycgenie.com/api/v1/responses/{response_id}/answers/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
for answer in resp.json()["results"]:
print(f"Q{answer['question_number']}: {answer['answer_text']}")
if answer["is_flagged"]:
print(" ^⚠ flagged")
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
result = client.questionnaire_responses.get_answers(
response_id="d67177bf-6871-45bf-85f2-95a1e52ccc64",
)
print(f"Answered: {result.summary.answered}/{result.summary.total_questions}")
print(f"Required unanswered: {result.summary.required_unanswered}")
print(f"Files uploaded: {result.summary.files_uploaded}/{result.summary.total_file_questions} ({result.summary.required_files_unanswered} required missing)")
for answer in result.text_answers:
print(f"Q{answer.question_number}: {answer.answer_text}")
if answer.is_flagged:
print(" ^⚠ flagged")
const responseId = 'd67177bf-6871-45bf-85f2-95a1e52ccc64';
const resp = await fetch(
`https://api.kycgenie.com/api/v1/responses/${responseId}/answers/`,
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const { results } = await resp.json();
results.forEach(a => {
console.log(`Q${a.question_number}: ${a.answer_text}`);
if (a.is_flagged) console.log(' ^ flagged');
});
const responseId = 'd67177bf-6871-45bf-85f2-95a1e52ccc64';
const resp = await fetch(
`https://api.kycgenie.com/api/v2/questionnaire-responses/${responseId}/answers/`,
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const { summary, text_answers, file_answers } = await resp.json();
console.log(`Answered: ${summary.answered}/${summary.total_questions}`);
console.log(`Required unanswered: ${summary.required_unanswered}`);
text_answers.forEach(a => {
console.log(`Q${a.question_number}: ${a.answer_text}`);
if (a.is_flagged) console.log(' ^ flagged');
});
Response Example
{
"summary": {
"total_questions": 83,
"answered": 45,
"required_unanswered": 12,
"total_file_questions": 5,
"files_uploaded": 3,
"required_files_unanswered": 2
},
"text_answers": [
{
"id": "aa11bb22-cc33-44dd-88ee-ff0011223344",
"question_id": 42,
"question_text": "Describe your encryption at rest.",
"question_number": 1,
"question_type": "text",
"section": "Security",
"sub_section": null,
"required": true,
"answer_text": "AES-256 encryption at rest and TLS 1.3 in transit.",
"date_answer": null,
"is_flagged": false,
"comments": [],
"num_of_comments": 0,
"created_at": "2026-01-21T14:10:00Z",
"updated_at": "2026-01-21T14:10:00Z"
}
],
"file_answers": []
}Answer Format by Question Type
Each question has a question_type that determines which field(s) to populate in an answer object and what value format is expected. The answer object is submitted inside the answers array when calling the Save Answers endpoints.
Always check the question_type on each question returned by Get Response Answers before constructing your answer payload. The options array (present on select and multi_select questions) lists the exact strings that are valid values.
| Question Type | Field to use | Expected format | Example value |
|---|---|---|---|
text | answer_text | Any string (max 2000 chars) | "Acme Corporation Ltd" |
email | answer_text | Valid email address | "jane@example.com" |
tel | answer_text | Phone number string | "+44 7700 900123" |
url | answer_text | URL string | "https://example.com" |
number | answer_text | Numeric string | "42" |
percentage | answer_text | Percentage as a numeric string (without % symbol) | "25.5" |
currency | answer_text | Pipe-separated amount and ISO 4217 currency code: "amount|CODE" | "1000000|USD" |
boolean | answer_text | "yes" or "no" | "yes" |
yes_no_na | answer_text | "yes", "no", or "na" | "na" |
select | answer_text | Exactly one option string from the question's options list | "Sole Trader" |
multi_select | answer_text | JSON array or JSON array string of one or more option strings from the question's options list | ["Option A","Option B"] |
country | answer_text | ISO 3166-1 alpha-2 code, ISO-3 code, or full country/nationality name - normalized to ISO-2 on save | "IE" or "Ireland" |
date | date_answer | ISO 8601 date string: YYYY-MM-DD | "1990-06-15" |
text_and_date | answer_text + date_answer | Free-text string in answer_text; date in date_answer (YYYY-MM-DD) | See example below |
address | answer_text | Object (or JSON string) with line1, line2 (optional), city, state (optional), postcode, country (ISO-2 or full name) | See example below |
structured | structured_data | JSON array of instance objects, one per row of the structured template | See example below |
Save Answers
Saves text answers and file uploads to a response without submitting. Use this to build up answers incrementally without triggering validation or autoflag.
/api/v1/responses/{response_id}/save/
Request Body (multipart/form-data)
| Parameter | Type | Required | Description |
|---|---|---|---|
answers | string (JSON) | No | JSON-encoded array of answer objects: [{"question_id": 123, "answer_text": "..."}] |
{file_question_id} | file | No | Files keyed by file question integer ID (e.g., field name 67 → file binary) |
Answer Object Fields
| Field | Type | Description |
|---|---|---|
question_id | integer | ID of the question being answered |
answer_text | string | The text answer |
date_answer | date | Optional: date answer (YYYY-MM-DD) |
Save Answers
curl -X PUT https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/save/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: save-acme-ddq-20260114" \
-F 'answers=[{"question_id": 42, "answer_text": "AES-256 encryption at rest"},{"question_id": 43, "answer_text": "Annual penetration testing"}]' \
-F "67=@audit_report.pdf"
# Step 1: save text answers (bare JSON array)
curl -X PUT https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/answers/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '[{"question_id": 42, "answer_text": "AES-256 encryption at rest"},{"question_id": 43, "answer_text": "Annual penetration testing"}]'
# Step 2: upload file for file question 67
curl -X PUT https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/answers/67/file/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@audit_report.pdf"
import json
import requests
response_id = "d67177bf-6871-45bf-85f2-95a1e52ccc64"
# v1: single multipart call for both text and files
resp = requests.put(
f"https://api.kycgenie.com/api/v1/responses/{response_id}/save/",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Idempotency-Key": "save-acme-ddq-20260114",
},
data={"answers": json.dumps([
{"question_id": 42, "answer_text": "AES-256 encryption at rest"},
{"question_id": 43, "answer_text": "Annual penetration testing"},
])},
files={"67": open("audit_report.pdf", "rb")},
)
print(resp.json())
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
response_id = "d67177bf-6871-45bf-85f2-95a1e52ccc64"
# Step 1: save text answers
result = client.questionnaire_responses.update_answers(
response_id=response_id,
answers=[
{"question_id": 42, "answer_text": "AES-256 encryption at rest"},
{"question_id": 43, "answer_text": "Annual penetration testing"},
],
)
print(f"Saved: {result.answers_saved} answers")
# Step 2: upload a file for file question 67
with open("audit_report.pdf", "rb") as f:
file_answer = client.questionnaire_responses.upload_file_answer(
response_id=response_id,
fq_id=67,
file=("audit_report.pdf", f, "application/pdf"),
)
print(f"Attachment ID: {file_answer.attachment_id}")
const responseId = 'd67177bf-6871-45bf-85f2-95a1e52ccc64';
const formData = new FormData();
formData.append('answers', JSON.stringify([
{ question_id: 42, answer_text: 'AES-256 encryption at rest' },
{ question_id: 43, answer_text: 'Annual penetration testing' },
]));
formData.append('67', fileInput.files[0]);
const resp = await fetch(`https://api.kycgenie.com/api/v1/responses/${responseId}/save/`, {
method: 'PUT',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Idempotency-Key': 'save-acme-ddq-20260114',
},
body: formData,
});
console.log(await resp.json());
const responseId = 'd67177bf-6871-45bf-85f2-95a1e52ccc64';
// Step 1: save text answers (bare JSON array)
await fetch(`https://api.kycgenie.com/api/v2/questionnaire-responses/${responseId}/answers/`, {
method: 'PUT',
headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify([
{ question_id: 42, answer_text: 'AES-256 encryption at rest' },
{ question_id: 43, answer_text: 'Annual penetration testing' },
]),
});
// Step 2: upload file for file question 67
const formData = new FormData();
formData.append('file', fileInput.files[0]);
await fetch(`https://api.kycgenie.com/api/v2/questionnaire-responses/${responseId}/answers/67/file/`, {
method: 'PUT',
headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
body: formData,
});
Response Example (200 OK)
{
"success": true,
"message": "Response progress saved",
"response_id": "d67177bf-6871-45bf-85f2-95a1e52ccc64",
"status": "DRAFT",
"answers_saved": 2,
"files_uploaded": 1,
"uploaded_files": [
{"file_question_id": 67, "filename": "audit_report.pdf", "document_type": "Audit Report"}
]
}
Send Response
Transitions a PREFILL response to DRAFT and sends an onboarding email to the subject entity. Creates an OnboardingSession with a unique time-limited access token.
Requirements
- Response status must be
PREFILL- DRAFT responses have already been sent - An email address must be available (entity on record, or supplied in request body)
- Consumes
send_ddqcredits from your plan
Request Body (optional, application/json)
| Field | Type | Required | Description |
|---|---|---|---|
email | string | No | Override email address. Falls back to the entity's email on record. Returns 400 if neither is available. |
expiry_hours | integer | No | Link expiry in hours. Defaults to tenant setting (typically 48). |
What Happens
- Response transitions from
PREFILL→DRAFT - Any existing AI autofill annotations are cleared from answers
- Onboarding email sent to entity with a unique time-limited link
- If a
pending_verification_configwas set on the response, an identity verification session is also created and bundled into the onboarding flow - Credits charged only after email dispatches successfully - if email fails the status reverts to
PREFILL
If a pending_verification_config was set on the response, steps will be ["ddq", "verification"] and verification_session_id will be populated with the session ID.
Send Response
# Minimal - uses entity's email on record
curl -X POST https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/send/ \
-H "Authorization: Bearer YOUR_API_KEY"
# With email override and custom expiry
curl -X POST https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/send/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "compliance@acmecorp.com", "expiry_hours": 72}'
# Minimal - uses entity's email on record
curl -X POST https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/send/ \
-H "Authorization: Bearer YOUR_API_KEY"
# With email override and custom expiry
curl -X POST https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/send/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "compliance@acmecorp.com", "expiry_hours": 72}'
import requests
response_id = "d67177bf-6871-45bf-85f2-95a1e52ccc64"
resp = requests.post(
f"https://api.kycgenie.com/api/v1/responses/{response_id}/send/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
# Optional - omit to use entity's email on record
json={"email": "compliance@acmecorp.com", "expiry_hours": 72},
)
data = resp.json()
print(f"Session: {data['session_id']}")
print(f"Expires: {data['expires_at']}")
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
result = client.questionnaire_responses.send(
response_id="d67177bf-6871-45bf-85f2-95a1e52ccc64",
# Optional - omit to use entity's email on record
email="compliance@acmecorp.com",
expiry_hours=72,
)
print(f"Session: {result.session_id}")
print(f"Steps: {result.steps}") # e.g. ['ddq'] or ['ddq', 'verification']
print(f"Expires: {result.expires_at}")
const resp = await fetch(
'https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/send/',
{
method: 'POST',
headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' },
// Optional body - omit to use entity's email on record
body: JSON.stringify({ email: 'compliance@acmecorp.com', expiry_hours: 72 }),
}
);
console.log(await resp.json());
const resp = await fetch(
'https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/send/',
{
method: 'POST',
headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'compliance@acmecorp.com', expiry_hours: 72 }),
}
);
console.log(await resp.json());
Response Example (200 OK)
{
"session_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "sent",
"email_sent": true,
"entity_id": "1b83f447-4a1e-4589-8a12-b8767e3c5426",
"entity_name": "Acme Corp",
"response_id": "d67177bf-6871-45bf-85f2-95a1e52ccc64",
"verification_session_id": null,
"steps": ["ddq"],
"expires_at": "2026-01-16T12:41:56.456529Z"
}
Submit Response
Submits a completed response for review. Transitions from DRAFT, PREFILL, CHANGES_REQUESTED, or RESUBMITTED to SUBMITTED status. All answers must be saved before calling this endpoint.
All required questions must have answers before submission. The API returns 400 with details about missing answers if validation fails.
response.autoflag_started and response.autoflag_completed are fired for all submissions - both API-driven and guest portal submissions.
In test mode, the autoflag pipeline is simulated - response.autoflag_completed webhooks are delivered with "simulated": true and no AI credits are consumed.
Submit Response
curl -X POST https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/submit/ \
-H "Authorization: Bearer YOUR_API_KEY"
curl -X POST https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/submit/ \
-H "Authorization: Bearer YOUR_API_KEY"
import requests
resp = requests.post(
"https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/submit/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(resp.json())
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
result = client.questionnaire_responses.submit(
response_id="d67177bf-6871-45bf-85f2-95a1e52ccc64",
)
print(f"Status: {result.status}") # SUBMITTED
print(f"Task: {result.autoflag_task_id}")
const resp = await fetch(
'https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/submit/',
{ method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
console.log(await resp.json());
Response Example
{
"success": true,
"message": "Response submitted successfully",
"response_id": "d67177bf-6871-45bf-85f2-95a1e52ccc64",
"status": "SUBMITTED",
"submitted_at": "2026-01-21T14:23:45Z",
"autoflag_task_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}Approve Response
Approve a submitted response, moving it to APPROVED status. This is the correct way to drive approval via API - not via the response notes endpoint.
Requirements
- Response must be in
SUBMITTED,UNDER_REVIEW,REVIEWED, orRESUBMITTEDstatus - API key must belong to your reviewer tenant, not the subject entity
What Happens
response.statuschanges toAPPROVED- Action is logged in the audit trail
- Webhook
response.status_changedis fired (if configured) - If
notesis provided, aResponseNoteis automatically created and tagged withstatus_transition: "APPROVED"
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
notes | string | No | Optional approval notes. If provided, a response note is automatically created and tagged with APPROVED. |
Error Responses
| Status | Description |
|---|---|
400 | Response is not in an approvable status |
404 | Response not found |
Approve Response
curl -X POST https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/approve/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"notes": "All checks passed. No adverse screening results."}'
curl -X POST https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/approve/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"notes": "All checks passed. No adverse screening results."}'
import requests
resp = requests.post(
"https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/approve/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"notes": "All checks passed. No adverse screening results."},
)
print(resp.json())
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
result = client.questionnaire_responses.approve(
response_id="d67177bf-6871-45bf-85f2-95a1e52ccc64",
notes="All checks passed. No adverse screening results.",
)
print(f"Status: {result.new_status}") # APPROVED
const resp = await fetch(
'https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/approve/',
{
method: 'POST',
headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({ notes: 'All checks passed. No adverse screening results.' }),
}
);
console.log(await resp.json());
Response Example
{
"status": "success",
"message": "Response approved successfully",
"response_id": "d67177bf-6871-45bf-85f2-95a1e52ccc64",
"old_status": "SUBMITTED",
"new_status": "APPROVED",
"notes": "All checks passed. No adverse screening results."
}Reject Response
Reject a submitted response, moving it to REJECTED status.
Requirements
- Response must be in
SUBMITTED,UNDER_REVIEW,REVIEWED, orRESUBMITTEDstatus
What Happens
response.statuschanges toREJECTED- Action is logged in the audit trail
- Webhook
response.status_changedis fired (if configured) - If
reasonis provided, aResponseNoteis automatically created and tagged withstatus_transition: "REJECTED"
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | No | Reason for rejection. Recommended for audit trail. If provided, a response note is automatically created. |
Error Responses
| Status | Description |
|---|---|
400 | Response is not in a rejectable status |
404 | Response not found |
Reject Response
curl -X POST https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/reject/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "Missing UBO documentation. Beneficial ownership chain unverifiable."}'
curl -X POST https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/reject/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "Missing UBO documentation. Beneficial ownership chain unverifiable."}'
import requests
resp = requests.post(
"https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/reject/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"reason": "Missing UBO documentation. Beneficial ownership chain unverifiable."},
)
print(resp.json())
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
result = client.questionnaire_responses.reject(
response_id="d67177bf-6871-45bf-85f2-95a1e52ccc64",
reason="Missing UBO documentation. Beneficial ownership chain unverifiable.",
)
print(f"Status: {result.new_status}") # REJECTED
const resp = await fetch(
'https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/reject/',
{
method: 'POST',
headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({ reason: 'Missing UBO documentation. Beneficial ownership chain unverifiable.' }),
}
);
console.log(await resp.json());
Response Example
{
"status": "success",
"message": "Response rejected successfully",
"response_id": "d67177bf-6871-45bf-85f2-95a1e52ccc64",
"old_status": "SUBMITTED",
"new_status": "REJECTED",
"reason": "Missing UBO documentation. Beneficial ownership chain unverifiable."
}Request Changes
Send a response back to the submitter requesting corrections or additional information. Moves the response to CHANGES_REQUESTED status - the submitter can then update answers and resubmit, which moves the response to RESUBMITTED.
Requirements
- Response must be in
SUBMITTED,UNDER_REVIEW,REVIEWED, orRESUBMITTEDstatus
What Happens
response.statuschanges toCHANGES_REQUESTED- Submitter receives an email notification (if email notifications are configured)
- Submitter can update answers and resubmit - response then moves to
RESUBMITTED - Action is logged in the audit trail
- If
changes_neededis provided, aResponseNoteis automatically created and tagged withstatus_transition: "CHANGES_REQUESTED"
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
changes_needed | string | No | Description of the required changes. Strongly recommended - this is included in the notification email to the submitter and logged as a note. |
Error Responses
| Status | Description |
|---|---|
400 | Response is not in a valid status for this action |
404 | Response not found |
Request Changes
curl -X POST https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/request-changes/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"changes_needed": "Please provide the latest audited financial statements and shareholder register."}'
curl -X POST https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/request-changes/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"changes_needed": "Please provide the latest audited financial statements and shareholder register."}'
import requests
resp = requests.post(
"https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/request-changes/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"changes_needed": "Please provide the latest audited financial statements and shareholder register."},
)
print(resp.json())
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
result = client.questionnaire_responses.request_changes(
response_id="d67177bf-6871-45bf-85f2-95a1e52ccc64",
changes_needed="Please provide the latest audited financial statements and shareholder register.",
)
print(f"Status: {result.new_status}") # CHANGES_REQUESTED
const resp = await fetch(
'https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/request-changes/',
{
method: 'POST',
headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({ changes_needed: 'Please provide the latest audited financial statements and shareholder register.' }),
}
);
console.log(await resp.json());
Response Example
{
"status": "success",
"message": "Changes requested successfully",
"response_id": "d67177bf-6871-45bf-85f2-95a1e52ccc64",
"old_status": "SUBMITTED",
"new_status": "CHANGES_REQUESTED",
"changes_needed": "Please provide the latest audited financial statements and shareholder register."
}Delete Response
Permanently deletes a response. This action cannot be undone.
Deleting a response removes all associated answers and file uploads. Consider archiving instead if you need to maintain records.
API Response
Returns 204 No Content on success. Some API clients may receive 200 OK - treat both as success.
Delete Response
curl -X DELETE https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/ \
-H "Authorization: Bearer YOUR_API_KEY"
curl -X DELETE https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/ \
-H "Authorization: Bearer YOUR_API_KEY"
import requests
response_id = "d67177bf-6871-45bf-85f2-95a1e52ccc64"
resp = requests.delete(
f"https://api.kycgenie.com/api/v1/responses/{response_id}/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(resp.status_code) # 204
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
client.questionnaire_responses.delete(
response_id="d67177bf-6871-45bf-85f2-95a1e52ccc64",
)
# Returns None on success (204 No Content)
const resp = await fetch(
'https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/',
{ method: 'DELETE', headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
console.log(resp.status); // 204
Get Response Entities
Returns all entities associated with a response's subject entity, traversing the relationship tree and grouping by type. Use this to gather all entity IDs before running batch screening.
Get Response Entities
curl https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/entities/ \
-H "Authorization: Bearer YOUR_API_KEY"
curl https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/entities/ \
-H "Authorization: Bearer YOUR_API_KEY"
import requests
response_id = "d67177bf-6871-45bf-85f2-95a1e52ccc64"
resp = requests.get(
f"https://api.kycgenie.com/api/v1/responses/{response_id}/entities/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(resp.json())
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
entities = client.questionnaire_responses.get_entities(
response_id="d67177bf-6871-45bf-85f2-95a1e52ccc64",
)
print(f"Total entities: {entities.total_count}")
const resp = await fetch(
'https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/entities/',
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
console.log(await resp.json());
Response Example
{
"subject_entity_id": "550e8400-e29b-41d4-a716-446655440000",
"subject_entity_name": "Acme Corporation",
"subject_entity_type": "company",
"companies": [{"id": "660f9511-f30c-23e5-b827-537766551111", "name": "Acme Subsidiary Ltd", "type": "company"}],
"individuals": [{"id": "770e0622-e41d-34f6-c938-648877662222", "name": "John Smith", "type": "individual"}],
"unlinked_entities": [],
"total_count": 2
}Regenerate Submit Token
Generates a fresh guest-access token for the response's onboarding session, invalidating the previous token. Requires the response to have been sent first via POST /responses/{id}/send/ - returns 404 if no active session exists.
The SDK model (SubmitToken) exposes onboarding_url as the canonical field. The raw API returns both onboarding_url and submit_url (identical values) for backward compatibility, but all new code should use onboarding_url.
Error Responses
- 404: No active onboarding session - call
POST /responses/{id}/send/first
Regenerate Submit Token
curl -X POST https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/token/ \
-H "Authorization: Bearer YOUR_API_KEY"
curl -X POST https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/token/ \
-H "Authorization: Bearer YOUR_API_KEY"
import requests
response_id = "d67177bf-6871-45bf-85f2-95a1e52ccc64"
# v1
resp = requests.post(
f"https://api.kycgenie.com/api/v1/responses/{response_id}/token/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = resp.json()
print(data["onboarding_url"])
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
token_data = client.questionnaire_responses.generate_token(
response_id="d67177bf-6871-45bf-85f2-95a1e52ccc64",
)
print(f"Onboarding URL: {token_data.onboarding_url}")
print(f"Expires at: {token_data.expires_at}")
const resp = await fetch(
'https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/token/',
{ method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const data = await resp.json();
console.log(data.onboarding_url);
const resp = await fetch(
'https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/token/',
{ method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const data = await resp.json();
console.log(data.onboarding_url);
Response Example
Autofill Response
Trigger AI-powered autofill for an entire response using documents linked to the subject entity. This is an asynchronous operation that returns immediately with a task ID for tracking.
Requirements
- Response status must be
PREFILLorDRAFT - Subject entity must have at least one document uploaded
- Consumes AI autofill credits from your plan
Behavior
- Asynchronous: Returns task_id immediately, processing happens in background
- Overwrites: Replaces existing answers (same as web UI behavior)
- Webhook: Sends
response.autofill_completedevent when finished
Autofill Response
curl -X POST https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/autofill/ \
-H "Authorization: Bearer YOUR_API_KEY"
curl -X POST https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/autofill/ \
-H "Authorization: Bearer YOUR_API_KEY"
import requests
response_id = "d67177bf-6871-45bf-85f2-95a1e52ccc64"
resp = requests.post(
f"https://api.kycgenie.com/api/v1/responses/{response_id}/autofill/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(resp.json())
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
result = client.questionnaire_responses.autofill(
response_id="d67177bf-6871-45bf-85f2-95a1e52ccc64",
)
print(f"Task ID: {result.task_id}")
print(f"Status: {result.status}") # PREFILL or DRAFT
const resp = await fetch(
'https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/autofill/',
{ method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
console.log(await resp.json());
Response (202 Accepted)
{
"task_id": "abc123-def456-789ghi",
"response_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "DRAFT",
"message": "Autofill task started successfully"
}Webhook Payload
{
"event": "response.autofill_completed",
"timestamp": "2026-01-28T13:21:55.853932+00:00",
"data": {
"response_id": "d67177bf-6871-45bf-85f2-95a1e52ccc64",
"response_name": "Acme Corp - DDQ - Template",
"questionnaire_id": "7f1a3c4e-5b9d-4e82-a1f3-8d2c5e7b9f0a",
"questionnaire_name": "DDQ - Template",
"entity_id": "1b83f447-4a1e-4589-8a12-b8767e3c5426",
"entity_name": "Acme Corp",
"status": "PREFILL",
"created_at": "2026-01-28T12:41:56.456529+00:00",
"submitted_at": null,
"answers_saved": 3,
"total_questions": 83,
"answers": [
{"question_id": "45", "question_text": "Legal name of the entity", "answer": "Acme Corporation Ltd"},
{"question_id": "46", "question_text": "Number of employees", "answer": "250"},
{"question_id": "47", "question_text": "List your directors", "answer": "2 instance(s)"}
]
}
}Generate Assessment Report
Generate a comprehensive AI-powered risk assessment report in DOCX format. The report includes entity details, screening results, answer analysis, and risk scoring based on all available data.
Requirements
- Response must be
SUBMITTED,UNDER_REVIEW,REVIEWED,APPROVED, orRESUBMITTED - Subject entity must exist
- Consumes AI assessment report credits from your plan
Behavior
- Asynchronous: Returns task_id immediately, report generation happens in background
- Storage: DOCX file linked to entity
- AI Analysis: Comprehensive risk assessment using all response data
- Includes: Entity details, screening results, flagged answers, risk sections, compliance summary
Error Responses
- 400: Response not submitted yet
- 404: Response or subject entity not found
- 500: Failed to queue assessment task
Generate Assessment Report
curl -X POST https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/assessment/generate/ \
-H "Authorization: Bearer YOUR_API_KEY"
curl -X POST https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/assessment/generate/ \
-H "Authorization: Bearer YOUR_API_KEY"
import requests
response_id = "d67177bf-6871-45bf-85f2-95a1e52ccc64"
resp = requests.post(
f"https://api.kycgenie.com/api/v1/responses/{response_id}/assessment/generate/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(resp.json())
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
result = client.questionnaire_responses.generate_assessment_report(
response_id="d67177bf-6871-45bf-85f2-95a1e52ccc64",
)
print(f"Task ID: {result.task_id}")
print(f"Status: {result.status}") # pending
const resp = await fetch(
'https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/assessment/generate/',
{ method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
console.log(await resp.json());
Response (202 Accepted)
{
"status": "pending",
"message": "Assessment report generation initiated",
"task_id": "abc123-def456-789ghi",
"response_id": "d67177bf-6871-45bf-85f2-95a1e52ccc64"
}Download Assessment Report
Download the most recently generated assessment report for a response as a DOCX file.
Requirements
- Assessment report must have been generated first using the generate endpoint
- Report must exist in storage
Error Responses
- 404: No assessment report found - generate one first using POST endpoint
- 500: Failed to download report from storage
Download Assessment Report
curl -L https://api.kycgenie.com/api/v1/responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/assessment/download/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-o assessment_report.docx
curl -L https://api.kycgenie.com/api/v2/questionnaire-responses/d67177bf-6871-45bf-85f2-95a1e52ccc64/assessment/download/ \
-H "Authorization: Bearer YOUR_API_KEY" \
-o assessment_report.docx
import requests
response_id = "d67177bf-6871-45bf-85f2-95a1e52ccc64"
resp = requests.get(
f"https://api.kycgenie.com/api/v1/responses/{response_id}/assessment/download/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
stream=True,
)
with open("assessment_report.docx", "wb") as fh:
for chunk in resp.iter_content(chunk_size=8192):
fh.write(chunk)
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
content = client.questionnaire_responses.download_assessment(
response_id="d67177bf-6871-45bf-85f2-95a1e52ccc64",
)
with open("assessment_report.docx", "wb") as fh:
fh.write(content)
const responseId = 'd67177bf-6871-45bf-85f2-95a1e52ccc64';
const resp = await fetch(`https://api.kycgenie.com/api/v1/responses/${responseId}/assessment/download/`, {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
});
const blob = await resp.blob();
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'assessment_report.docx';
link.click();
const responseId = 'd67177bf-6871-45bf-85f2-95a1e52ccc64';
const resp = await fetch(`https://api.kycgenie.com/api/v2/questionnaire-responses/${responseId}/assessment/download/`, {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
});
const blob = await resp.blob();
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'assessment_report.docx';
link.click();
Response Headers
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.documentContent-Disposition: attachment; filename="assessment_report_{response_id}.docx"
File Answers
List, retrieve, download, and delete file attachments on a response. To upload a file to a slot-based file question, use PUT /answers/{fq_id}/file/ documented in the Save Answers section.
Use list to inspect all uploaded file answers, get to fetch a single attachment's metadata, download to get a time-limited URL, and delete to remove an attachment no longer needed.
Supported Actions
- List all file answers on a response
- Get a single attachment's metadata
- Generate a time-limited download URL
- Delete an attachment when it is no longer needed
List File Answers
Retrieve all file attachments on a response, including document metadata and file question linkage.
/api/v2/questionnaire-responses/{response_id}/answers/files/List File Answers
curl https://api.kycgenie.com/api/v2/questionnaire-responses/770g0611-g40d-63f6-c938-668877662222/answers/files/ \
-H "Authorization: Bearer YOUR_API_KEY"
import requests
response_id = "770g0611-g40d-63f6-c938-668877662222"
resp = requests.get(
f"https://api.kycgenie.com/api/v2/questionnaire-responses/{response_id}/answers/files/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(resp.json())
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
files = client.questionnaire_responses.list_file_answers(
response_id="770g0611-g40d-63f6-c938-668877662222",
)
for f in files.data:
print(f"{f.document.name} - {f.file_question_type}")
const responseId = '770g0611-g40d-63f6-c938-668877662222';
const resp = await fetch(
`https://api.kycgenie.com/api/v2/questionnaire-responses/${responseId}/answers/files/`,
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
console.log(await resp.json());
Response Example
{
"count": 1,
"data": [
{
"attachment_id": "abb6336c-7edc-4814-92eb-4a80dc5b17fd",
"response_id": "770g0611-g40d-63f6-c938-668877662222",
"document": {
"id": "880g1733-h50e-74g7-d049-779988773333",
"name": "bank_statement_jan_2026.pdf",
"size": 524288,
"content_type": "application/pdf",
"document_type_name": "Bank Statement",
"classification": "entity_private",
"uploaded_by_email": "api.user@example.com",
"created_at": "2026-01-30T15:10:22Z",
"expiry_date": null
},
"file_question_id": 67,
"file_question_type": "Bank Statement",
"file_question_description": "Upload your most recent bank statement (last 3 months)",
"file_question_required": true,
"file_question_multiple": false,
"is_flagged": false,
"is_submitted": false,
"created_at": "2026-01-30T15:10:22Z",
"updated_at": "2026-01-30T15:10:22Z"
}
]
}Get File Answer
Retrieve metadata for a single file attachment.
/api/v2/questionnaire-responses/{response_id}/answers/files/{attachment_id}/Path Parameters
| Parameter | Type | Description |
|---|---|---|
response_id | UUID | The response's unique identifier |
attachment_id | UUID | The attachment_id from the list endpoint |
Get File Answer
curl https://api.kycgenie.com/api/v2/questionnaire-responses/770g0611-g40d-63f6-c938-668877662222/answers/files/abb6336c-7edc-4814-92eb-4a80dc5b17fd/ \
-H "Authorization: Bearer YOUR_API_KEY"
import requests
response_id = "770g0611-g40d-63f6-c938-668877662222"
attachment_id = "abb6336c-7edc-4814-92eb-4a80dc5b17fd"
resp = requests.get(
f"https://api.kycgenie.com/api/v2/questionnaire-responses/{response_id}/answers/files/{attachment_id}/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(resp.json())
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
fa = client.questionnaire_responses.get_file_answer(
response_id="770g0611-g40d-63f6-c938-668877662222",
attachment_id="abb6336c-7edc-4814-92eb-4a80dc5b17fd",
)
print(f"{fa.document.name} - question: {fa.file_question_type}")
const resp = await fetch(
'https://api.kycgenie.com/api/v2/questionnaire-responses/770g0611-g40d-63f6-c938-668877662222/answers/files/abb6336c-7edc-4814-92eb-4a80dc5b17fd/',
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
console.log(await resp.json());
Download File Answer
Generate a short-lived pre-signed URL to download a file attachment.
/api/v2/questionnaire-responses/{response_id}/answers/files/{attachment_id}/download/Returns a time-limited URL valid for 15 minutes
Path Parameters
| Parameter | Type | Description |
|---|---|---|
response_id | UUID | The response's unique identifier |
attachment_id | UUID | The attachment_id from the list endpoint (not document.id) |
Download File Answer
curl https://api.kycgenie.com/api/v2/questionnaire-responses/770g0611-g40d-63f6-c938-668877662222/answers/files/abb6336c-7edc-4814-92eb-4a80dc5b17fd/download/ \
-H "Authorization: Bearer YOUR_API_KEY"
import requests
response_id = "770g0611-g40d-63f6-c938-668877662222"
attachment_id = "abb6336c-7edc-4814-92eb-4a80dc5b17fd"
resp = requests.get(
f"https://api.kycgenie.com/api/v2/questionnaire-responses/{response_id}/answers/files/{attachment_id}/download/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(resp.json())
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
result = client.questionnaire_responses.download_file(
response_id="770g0611-g40d-63f6-c938-668877662222",
attachment_id="abb6336c-7edc-4814-92eb-4a80dc5b17fd",
)
print(f"Download URL: {result.download_url}")
print(f"Expires at: {result.expires_at}")
const responseId = '770g0611-g40d-63f6-c938-668877662222';
const attachmentId = 'abb6336c-7edc-4814-92eb-4a80dc5b17fd';
const resp = await fetch(
`https://api.kycgenie.com/api/v2/questionnaire-responses/${responseId}/answers/files/${attachmentId}/download/`,
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
console.log(await resp.json());
Response Example
{
"download_url": "https://app.kycgenie.com/api/v1/files/dl_C2e4f6a8b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4/",
"expires_at": "2026-02-20T15:30:00+00:00",
"attachment_id": "abb6336c-7edc-4814-92eb-4a80dc5b17fd",
"filename": "bank_statement_jan_2026.pdf",
"content_type": "application/pdf",
"size": 524288
}Delete File Answer
/api/v2/questionnaire-responses/{response_id}/answers/files/{attachment_id}/Response (204 No Content)
No response body on success.
Delete File Answer
curl -X DELETE https://api.kycgenie.com/api/v2/questionnaire-responses/770g0611-g40d-63f6-c938-668877662222/answers/files/abb6336c-7edc-4814-92eb-4a80dc5b17fd/ \
-H "Authorization: Bearer YOUR_API_KEY"
import requests
response_id = "770g0611-g40d-63f6-c938-668877662222"
attachment_id = "abb6336c-7edc-4814-92eb-4a80dc5b17fd"
resp = requests.delete(
f"https://api.kycgenie.com/api/v2/questionnaire-responses/{response_id}/answers/files/{attachment_id}/",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
print(resp.status_code) # 204
from kycgenie import KYCGenie
client = KYCGenie(api_key="YOUR_API_KEY")
client.questionnaire_responses.delete_file_answer(
response_id="770g0611-g40d-63f6-c938-668877662222",
attachment_id="abb6336c-7edc-4814-92eb-4a80dc5b17fd",
)
# Returns None on success (204 No Content)
const responseId = '770g0611-g40d-63f6-c938-668877662222';
const attachmentId = 'abb6336c-7edc-4814-92eb-4a80dc5b17fd';
const resp = await fetch(
`https://api.kycgenie.com/api/v2/questionnaire-responses/${responseId}/answers/files/${attachmentId}/`,
{ method: 'DELETE', headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
console.log(resp.status); // 204