Flowmingo exposes a lightweight REST surface so you can trigger interview invitations from your ATS, CRM, or internal tooling. This section explains how to create API keys and use them to call Flowmingo's integration APIs.
1. Prerequisites
- Workspace access: You need access to Settings -> Integrations -> API Keys in the company workspace.
- Interview set and/or project ID: Keep the UUID you will target (
com_interview_set_idand/orcom_project_id). - CV file hosting (if sending CVs): Upload CVs to publicly accessible HTTPS storage (or signed URLs) that Flowmingo can fetch.
2. Request an API key
2.1 Open the API Keys panel
- Sign in at
https://team.flowmingo.ai. - Open your avatar menu -> Settings.
- Go to Integrations -> API Keys.
2.2 Create a key
- Click Create API Key.
- Provide optional Name and Description.
- (Optional, recommended) add scopes such as
invite_candidatesorcreate_set. - Click Generate.
You will receive a one-time secret with format like fl_live_abcd12345.<secret>. Copy and store it immediately. After creation, key values are masked (for example: fl_live_abcd12345.****).
2.3 Update metadata
Use Edit on an existing key to update:
namedescriptionscopesstatus
2.4 Rotate or revoke
Delete a key to revoke access immediately, then create a new key and update your integrations.
2.5 Security notes
Store API keys in a secret manager (AWS Secrets Manager, GCP Secret Manager, 1Password, etc.). Never embed keys in frontend code, logs, or version control.
2.6 Authenticate your requests
Send the key in the X-API-Key header:
X-API-Key: fl_live_abcd12345.n8G3zPNw9gWkK1Kj
Authorization: Bearer <key>is NOT accepted by this API. It is a common habit, and it fails with a 401 whose message is"API key is required"— which reads like the key is missing even though you sent one. If you prefer theAuthorizationheader, the scheme must be the literal wordApiKey:Authorization: ApiKey fl_live_.... (Our MCP server atmcp.flowmingo.aidoes acceptBearer; this REST API does not.)
To check a key without side effects, call the introspection endpoint — it needs no scope, so any valid key can verify itself:
curl -s -H 'X-API-Key: fl_live_abcd12345.n8G3zPNw9gWkK1Kj' \
https://apis.flowmingo.ai/company/integration/me/v1
{
"api_key_id": "0b0a...",
"organization_id": 12345,
"scopes": ["read_hiring", "read_candidates", "write_pipeline", "invite_candidates", "create_set"],
"expires_at": null
}
A 200 proves the key is accepted and shows which organization and scopes it carries. Anything else, see section 6, Troubleshooting.
3. Create interview sets through the API
Use this endpoint to programmatically provision an interview set (interview or CV set) along with its questions, requirements, and access list.
3.1 Endpoint & environments
| Environment | Settings URL | API |
|---|---|---|
| Production | https://team.flowmingo.ai |
https://apis.flowmingo.ai/company/integration/interview/set/v1 |
Method: POST. Required API key scope: create_set.
3.2 Request payload
| Field | Type | Required | Notes |
|---|---|---|---|
title |
string | Yes | Max 1024 chars. Display title for the interview set. |
position |
string | Optional | Max 1024 chars. The job title / role being hired for. |
set_type |
int | Optional | 1 = interview (default), 2 = CV set. |
description |
string | Optional | Free-text description. |
interview_duration |
int | Optional | Duration in minutes. |
cfg_lingual_ids |
int[] | Optional | Languages the interview may be conducted in. 290 = English, 204 = Hindi (India); see 3.2.4 below. Omit it and English is applied. |
number_of_retakes |
int | Optional | Number of allowed retakes for candidates. |
priority |
int | Optional | Sort/priority weighting. |
project_id |
UUID | Optional | If provided, the interview set is attached to this project. |
iai_questions |
array | Optional | Interview questions (see 3.2.1). |
iai_requirements |
array | Optional | Evaluation requirements (see 3.2.2). |
com_accesses |
array | Optional | Recruiter/collaborator access entries (see 3.2.3). |
3.2.1 iai_questions[] item
| Field | Type | Required | Notes |
|---|---|---|---|
title |
string | Optional | Max 5000 chars. |
content |
string | Optional | Max 5000 chars. The question prompt. |
hint |
string | Optional | Max 2000 chars. |
details |
string | Optional | Max 10000 chars. Long-form context / instructions. |
question_mode |
string | Optional | One of pre_screening, interview. Defaults to interview. Mapped server-side to cfg_question_mode_id. |
question_type |
string | Optional | One of text, checkbox, radio, file. Defaults to text. Mapped server-side to cfg_question_type_id. |
priority |
int | Optional | Order weight. |
options |
array | Optional | Choice options: { id, title, titles, content, contents, priority }. |
attributes |
object | Optional | Arbitrary metadata. |
3.2.2 iai_requirements[] item
| Field | Type | Required | Notes |
|---|---|---|---|
title |
string | Optional | Max 5000 chars. |
content |
string | Optional | Max 5000 chars. Requirement description. |
hint |
string | Optional | Max 2000 chars. |
cfg_importance_id |
int | Optional | Importance level — see below. |
tal_skill_ids |
int[] | Optional | Skill IDs linked to this requirement. |
priority |
int | Optional | Order weight. |
attributes |
object | Optional | Arbitrary metadata. |
cfg_importance_id values. These are configured IDs, not a 1-4 scale, and the lowest ID is the
weakest importance. Importance sets how heavily the requirement counts toward the candidate's
evaluation score. The ID selects the importance, and the importance carries the weight:
cfg_importance_id |
Importance | Weight in the evaluation score |
|---|---|---|
244 |
Good To Have | 1x |
245 |
Important | 2x |
272 |
Very Important | 3x |
273 |
Must Have | 4x |
If omitted, or if the value is not one of the four above, the requirement is stored as 244
(Good To Have).
3.2.3 com_accesses[] item
| Field | Type | Required | Notes |
|---|---|---|---|
email |
string | Yes | Valid email, max 250 chars. Identifies the access target. |
fullname |
string | Optional | Max 500 chars. |
firstname |
string | Optional | Max 250 chars. |
lastname |
string | Optional | Max 250 chars. |
priority |
int | Optional | Order weight. |
attributes |
object | Optional | Arbitrary metadata. |
3.2.4 cfg_lingual_ids values
Like cfg_importance_id, these are configured IDs rather than a sequence, so the two you are most
likely to need are listed here:
cfg_lingual_ids |
Language |
|---|---|
290 |
English |
204 |
Hindi (India) |
Send them as an array of integers, e.g. [290] or [290, 204]. The array is the set of languages a
candidate is allowed to conduct the interview in.
If you omit the field, English is applied for you. That default is best-effort, so send [290]
explicitly if your integration depends on the language being set deterministically. CV sets
(set_type: 2) have no interview, so the field is ignored for them.
Need an ID for a language that is not listed? Ask us rather than guessing — a value that is not a real configuration ID is stored as-is and then resolves to nothing.
Evaluation report language is a separate setting that this endpoint's documented contract does not cover, so do not rely on setting it here. If none is ever stored, reports come out in English. Once a language has been stored for a set it is fixed — a later attempt to change it is ignored rather than rejected, because reports already written are never regenerated.
3.3 Sample request
curl -X POST https://apis.flowmingo.ai/company/integration/interview/set/v1 \
-H 'Content-Type: application/json' \
-H 'X-API-Key: fl_live_abcd12345.n8G3zPNw9gWkK1Kj' \
-d '{
"title": "Senior Backend Engineer - Async Interview",
"set_type": 1,
"description": "Async interview for senior backend candidates.",
"interview_duration": 30,
"number_of_retakes": 1,
"project_id": "019c5ea9-a792-7f0d-b671-4c3c1602094e",
"iai_questions": [
{
"title": "System design",
"content": "Walk us through how you would design a rate limiter.",
"question_mode": "interview",
"question_type": "text",
"priority": 1
}
],
"iai_requirements": [
{
"title": "5+ years backend experience",
"cfg_importance_id": 273,
"priority": 1
}
],
"com_accesses": [
{ "email": "recruiter@example.com", "fullname": "Jane Recruiter" }
]
}'
import fetch from 'node-fetch'
const response = await fetch('https://apis.flowmingo.ai/company/integration/interview/set/v1', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'fl_live_abcd12345.n8G3zPNw9gWkK1Kj'
},
body: JSON.stringify({
title: 'Senior Backend Engineer - Async Interview',
set_type: 1,
interview_duration: 30,
number_of_retakes: 1,
project_id: '019c5ea9-a792-7f0d-b671-4c3c1602094e',
iai_questions: [
{
title: 'System design',
content: 'Walk us through how you would design a rate limiter.',
question_mode: 'interview',
question_type: 'text',
priority: 1
}
],
iai_requirements: [{ title: '5+ years backend experience', cfg_importance_id: 273, priority: 1 }],
com_accesses: [{ email: 'recruiter@example.com', fullname: 'Jane Recruiter' }]
})
})
const data = await response.json()
console.log(data)
import requests
response = requests.post(
"https://apis.flowmingo.ai/company/integration/interview/set/v1",
headers={
"Content-Type": "application/json",
"X-API-Key": "fl_live_abcd12345.n8G3zPNw9gWkK1Kj",
},
json={
"title": "Senior Backend Engineer - Async Interview",
"set_type": 1,
"interview_duration": 30,
"number_of_retakes": 1,
"project_id": "019c5ea9-a792-7f0d-b671-4c3c1602094e",
"iai_questions": [
{
"title": "System design",
"content": "Walk us through how you would design a rate limiter.",
"question_mode": "interview",
"question_type": "text",
"priority": 1,
}
],
"iai_requirements": [
{"title": "5+ years backend experience", "cfg_importance_id": 273, "priority": 1}
],
"com_accesses": [{"email": "recruiter@example.com", "fullname": "Jane Recruiter"}],
},
)
print(response.json())
<?php
$payload = [
'title' => 'Senior Backend Engineer - Async Interview',
'set_type' => 1,
'interview_duration' => 30,
'number_of_retakes' => 1,
'project_id' => '019c5ea9-a792-7f0d-b671-4c3c1602094e',
'iai_questions' => [
[
'title' => 'System design',
'content' => 'Walk us through how you would design a rate limiter.',
'question_mode' => 'interview',
'question_type' => 'text',
'priority' => 1,
],
],
'iai_requirements' => [
['title' => '5+ years backend experience', 'cfg_importance_id' => 273, 'priority' => 1],
],
'com_accesses' => [
['email' => 'recruiter@example.com', 'fullname' => 'Jane Recruiter'],
],
];
$ch = curl_init('https://apis.flowmingo.ai/company/integration/interview/set/v1');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: fl_live_abcd12345.n8G3zPNw9gWkK1Kj',
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
payload := map[string]any{
"title": "Senior Backend Engineer - Async Interview",
"set_type": 1,
"interview_duration": 30,
"number_of_retakes": 1,
"project_id": "019c5ea9-a792-7f0d-b671-4c3c1602094e",
"iai_questions": []map[string]any{
{
"title": "System design",
"content": "Walk us through how you would design a rate limiter.",
"question_mode": "interview",
"question_type": "text",
"priority": 1,
},
},
"iai_requirements": []map[string]any{
{"title": "5+ years backend experience", "cfg_importance_id": 273, "priority": 1},
},
"com_accesses": []map[string]any{
{"email": "recruiter@example.com", "fullname": "Jane Recruiter"},
},
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest(
"POST",
"https://apis.flowmingo.ai/company/integration/interview/set/v1",
bytes.NewReader(body),
)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", "fl_live_abcd12345.n8G3zPNw9gWkK1Kj")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://apis.flowmingo.ai/company/integration/interview/set/v1')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['X-API-Key'] = 'fl_live_abcd12345.n8G3zPNw9gWkK1Kj'
request.body = {
title: 'Senior Backend Engineer - Async Interview',
set_type: 1,
interview_duration: 30,
number_of_retakes: 1,
project_id: '019c5ea9-a792-7f0d-b671-4c3c1602094e',
iai_questions: [
{
title: 'System design',
content: 'Walk us through how you would design a rate limiter.',
question_mode: 'interview',
question_type: 'text',
priority: 1
}
],
iai_requirements: [
{ title: '5+ years backend experience', cfg_importance_id: 273, priority: 1 }
],
com_accesses: [
{ email: 'recruiter@example.com', fullname: 'Jane Recruiter' }
]
}.to_json
response = http.request(request)
puts response.body
3.4 Sample response
The endpoint returns a results envelope.
Treat the response as an acknowledgement, not a confirmation.
resultsis built from the payload you submitted rather than re-read from the saved record, so a field can appear in the response and not be stored — acfg_importance_idoutside the four valid IDs is echoed back to you unchanged and stored as244. To confirm what actually persisted, fetch the interview set after creating it.
{
"results": {
"id": "019c6010-2f3a-7a91-9b70-4c1a6f57c8ab",
"title": "Senior Backend Engineer - Async Interview",
"set_type": 1,
"interview_duration": 30,
"number_of_retakes": 1,
"com_project_id": "019c5ea9-a792-7f0d-b671-4c3c1602094e",
"iai_questions": [
{
"id": "019c6010-3a01-77e2-9d2c-2d4a8b41f13b",
"title": "System design",
"content": "Walk us through how you would design a rate limiter.",
"cfg_question_mode_id": 248,
"cfg_question_type_id": 250,
"priority": 1
}
],
"iai_requirements": [
{
"id": "019c6010-3b1f-7ce9-86c4-6c0aa1c3f9d2",
"title": "5+ years backend experience",
"cfg_importance_id": 273,
"priority": 1
}
],
"com_accesses": [
{
"id": "019c6010-3c4d-71b8-9e9b-9f0d2c4b8e51",
"email": "recruiter@example.com",
"fullname": "Jane Recruiter"
}
]
}
}
4. Invite candidates through the API
4.1 Endpoint & environments
| Environment | Settings URL | API |
|---|---|---|
| Production | https://team.flowmingo.ai |
https://apis.flowmingo.ai/company/integration/interview/candidate/invite/v1 |
4.2 Request payload
| Field | Type | Required | Notes |
|---|---|---|---|
com_interview_set_id |
UUID | Conditionally | Required if com_project_id is not provided. |
com_project_id |
UUID | Conditionally | Required if com_interview_set_id is not provided. |
com_job_post_id |
UUID | Optional | If omitted while com_project_id is provided, the latest job post in that project is selected. |
candidates |
array | Yes | Must contain at least 1 candidate. |
candidates[].ats_candidate_id |
UUID | Optional | Must be unique within a single request. |
candidates[].email |
string | Optional | Required for invite creation; if missing and cv_link exists, the request queues candidate-entry CV processing instead. |
candidates[].name |
string | Optional | Candidate display name. |
candidates[].firstname |
string | Optional | Candidate first name override. |
candidates[].lastname |
string | Optional | Candidate last name override. |
candidates[].cv_link |
string URL | Optional | CV source URL for evaluation workflows. |
invitation_message |
string (>=10 chars) | Optional | Custom message content. |
send_invite |
boolean | Optional (default true) |
When true, invitation records are created for interview-set flows. |
4.3 Sample request
curl -X POST https://apis.flowmingo.ai/company/integration/interview/candidate/invite/v1 \
-H 'Content-Type: application/json' \
-H 'X-API-Key: fl_live_abcd12345.n8G3zPNw9gWkK1Kj' \
-d '{
"com_interview_set_id": "019c5ea9-a792-7f0d-b671-4c3c1602094e",
"candidates": [
{
"name": "Alex Doe",
"email": "alex@example.com",
"cv_link": "https://storage.googleapis.com/flowmingo-demo/cv/alex.pdf"
}
],
"invitation_message": "Hi {{name}}, please complete the async interview in the next 48 hours.",
"send_invite": true
}'
import fetch from 'node-fetch'
const response = await fetch('https://apis.flowmingo.ai/company/integration/interview/candidate/invite/v1', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'fl_live_abcd12345.n8G3zPNw9gWkK1Kj'
},
body: JSON.stringify({
com_interview_set_id: '019c5ea9-a792-7f0d-b671-4c3c1602094e',
candidates: [
{
name: 'Alex Doe',
email: 'alex@example.com',
cv_link: 'https://storage.googleapis.com/flowmingo-demo/cv/alex.pdf'
}
],
invitation_message: 'Hi {{name}}, please complete the async interview in the next 48 hours.',
send_invite: true
})
})
const data = await response.json()
console.log(data)
import requests
response = requests.post(
"https://apis.flowmingo.ai/company/integration/interview/candidate/invite/v1",
headers={
"Content-Type": "application/json",
"X-API-Key": "fl_live_abcd12345.n8G3zPNw9gWkK1Kj",
},
json={
"com_interview_set_id": "019c5ea9-a792-7f0d-b671-4c3c1602094e",
"candidates": [
{
"name": "Alex Doe",
"email": "alex@example.com",
"cv_link": "https://storage.googleapis.com/flowmingo-demo/cv/alex.pdf",
}
],
"invitation_message": "Hi {{name}}, please complete the async interview in the next 48 hours.",
"send_invite": True,
},
)
print(response.json())
<?php
$payload = [
'com_interview_set_id' => '019c5ea9-a792-7f0d-b671-4c3c1602094e',
'candidates' => [
[
'name' => 'Alex Doe',
'email' => 'alex@example.com',
'cv_link' => 'https://storage.googleapis.com/flowmingo-demo/cv/alex.pdf',
],
],
'invitation_message' => 'Hi {{name}}, please complete the async interview in the next 48 hours.',
'send_invite' => true,
];
$ch = curl_init('https://apis.flowmingo.ai/company/integration/interview/candidate/invite/v1');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: fl_live_abcd12345.n8G3zPNw9gWkK1Kj',
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
payload := map[string]any{
"com_interview_set_id": "019c5ea9-a792-7f0d-b671-4c3c1602094e",
"candidates": []map[string]any{
{
"name": "Alex Doe",
"email": "alex@example.com",
"cv_link": "https://storage.googleapis.com/flowmingo-demo/cv/alex.pdf",
},
},
"invitation_message": "Hi {{name}}, please complete the async interview in the next 48 hours.",
"send_invite": true,
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest(
"POST",
"https://apis.flowmingo.ai/company/integration/interview/candidate/invite/v1",
bytes.NewReader(body),
)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", "fl_live_abcd12345.n8G3zPNw9gWkK1Kj")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://apis.flowmingo.ai/company/integration/interview/candidate/invite/v1')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['X-API-Key'] = 'fl_live_abcd12345.n8G3zPNw9gWkK1Kj'
request.body = {
com_interview_set_id: '019c5ea9-a792-7f0d-b671-4c3c1602094e',
candidates: [
{
name: 'Alex Doe',
email: 'alex@example.com',
cv_link: 'https://storage.googleapis.com/flowmingo-demo/cv/alex.pdf'
}
],
invitation_message: 'Hi {{name}}, please complete the async interview in the next 48 hours.',
send_invite: true
}.to_json
response = http.request(request)
puts response.body
4.4 Sample response
The endpoint returns a response envelope with per-candidate results:
{
"results": [
{
"email_address": "alex@example.com",
"cv_link": "https://storage.googleapis.com/flowmingo-demo/cv/alex.pdf",
"invitation": {
"id": "019c5eb3-fb50-707b-9833-0862efa4b275",
"email": "alex@example.com",
"status_text": "pending"
},
"submission": {
"id": "019c5eb4-55ab-76fc-90ec-98930c56a7bd",
"status_text": "need_interview"
}
}
]
}
Error details are returned per candidate in fields such as error_message, invitation_error_message, application_error_message, submission_error_message, and candidate_entry_error_message.
5. Read results through the API
These endpoints are read-only. They are how you pull scores and the AI's written evaluation into your own system, instead of sending people to Flowmingo to read a report.
5.1 What you can read
All paths below are prefixed with https://apis.flowmingo.ai/company/integration.
| Endpoint | Scope | Returns |
|---|---|---|
GET /me/v1 |
any valid key | The key's organization and scopes (see section 2.6). |
GET /hiring/submissions/v1 |
read_hiring |
A page of submissions. Use it to find a submission_id. |
GET /hiring/submissions/{id}/detail/v1 |
read_hiring |
One submission, with the AI evaluation. |
GET /hiring/submissions/{id}/v1 |
read_hiring |
One submission, without the AI evaluation. |
GET /hiring/interview-sets/v1 |
read_hiring |
Your interview sets. |
GET /hiring/job-posts/v1, GET /hiring/job-posts/{id}/v1 |
read_hiring |
Your job posts. |
GET /hiring/projects/v1, GET /hiring/projects/{projectId}/stages/v1 |
read_hiring |
Projects and their pipeline stages. |
GET /hiring/applications/v1 |
read_hiring |
Pipeline applications. |
GET /hiring/interviews/upcoming/v1 |
read_hiring |
Invited, not yet completed. |
GET /hiring/overview/v1 |
read_hiring |
Aggregate counts for a funnel view. |
GET /hiring/candidates/v1, GET /hiring/candidates/{id}/v1 |
read_candidates |
Candidate name, email and CV link. The single-candidate route also returns phone and thumb_cv_link. |
GET /events/v1 |
read_hiring |
Your webhook events, cursor-paginated. Poll these if you cannot host an endpoint. |
Every read is scoped to the organization that owns the key — no endpoint can return another organization's
data. The id-addressed routes (job-posts/{id}, submissions/{id}, submissions/{id}/detail,
candidates/{id}) answer 404 for an id you do not own; the list routes simply return no matching rows.
Candidate names and emails require
read_candidates.read_hiringreturns ids, scores and evaluations. Withoutread_candidates, thecandidate_name,firstname,lastnameandGET /me/v1shows what your key holds.
5.2 Find a submission
curl -s -H 'X-API-Key: fl_live_abcd12345.n8G3zPNw9gWkK1Kj' \
'https://apis.flowmingo.ai/company/integration/hiring/submissions/v1?com_interview_set_id=019bcb66-2d6e-795a-9770-148355e51c51&limit=20'
| Query param | Type | Notes |
|---|---|---|
com_interview_set_id |
uuid | Only submissions for this interview set. |
tal_candidate_id |
uuid | Only submissions for this candidate. |
sort_by |
string | created_at (default) or overall_score. Rows with no score always sort last, whichever direction you ask for. |
order |
string | asc or desc (default desc). |
completed_within_days |
integer | Completed in the last N days (max 3650). Ignored if you also send completed_from. |
completed_from, completed_to |
ISO date | Completion window. A bare YYYY-MM-DD in completed_to covers that whole day; a full timestamp is used as given. |
with_project |
boolean | Send true or 1. Adds com_project_id to each row — it is null for a set that maps to no single project. |
page, limit |
integer | page defaults to 1 (max 100000), limit to 20 (max 100). Both must be positive: limit=0 is a 400, not "use the default". |
The response is { "data": [ ... ], "total": n, "page": 1, "limit": 20 }. Each row carries id (the
submission id), status, com_interview_set_id, tal_candidate_id, assessment_type, assessment_name,
overall_score, score_status, submission_at and created_at — plus candidate_name, firstname,
lastname and email when your key carries read_candidates. score_percent appears only on a released
skill-test row that has a score; test for the key rather than for null.
5.3 Read the evaluation
curl -s -H 'X-API-Key: fl_live_abcd12345.n8G3zPNw9gWkK1Kj' \
'https://apis.flowmingo.ai/company/integration/hiring/submissions/{submission_id}/detail/v1'
{
"id": "59065d06-a013-44c2-97d2-88e9d6b3c858",
"submission_id": "59065d06-a013-44c2-97d2-88e9d6b3c858",
"status": 1,
"com_interview_set_id": "adf40c5c-ba95-4aa4-8804-1b309014df59",
"tal_candidate_id": "019bcb66-66d9-781e-96cc-e4a2b74c7fce",
"assessment_type": "ai_interview",
"assessment_name": "AI Engineer - AI Interview",
"candidate_name": "Alex Kim",
"firstname": "Alex",
"lastname": "Kim",
"email": "alex@example.com",
"overall_score": 7.2,
"score_status": "scored",
"submission_at": "2026-09-10T12:00:49.000Z",
"created_at": "2026-09-10T11:14:02.000Z",
"evaluation": {
"summary": "A strong applied engineer who has shipped retrieval systems end to end...",
"overall_rating": 7.2,
"strengths": ["Ships production RAG pipelines", "..."],
"gaps": ["Limited exposure to evaluation tooling", "..."],
"recommendations": ["Probe observability practices in a follow-up", "..."],
"competencies": [
{ "name": "Applied AI/ML Engineering", "rating": 9 },
{ "name": "Conversational AI", "rating": 3 }
]
},
"evaluation_cv": { "summary": "...", "overall_rating": 7.9 }
}
overall_scoreis out of 10, and so is everycompetencies[].rating. It isnullwhen there is no usable number —score_statussays which:scored,cannot_evaluate(the AI ran and could not rate the attempt) ornot_scored(no score yet). Acannot_evaluateis not a zero.statusis a number:0inactive,1active (complete),2pending,3archived,4incomplete,5needs interview,6retaking.assessment_typeis one ofai_interview,cv_evaluation,skill_test,showcase,unknown. A skill test also carriesscore_percent(the real percentage), becauseoverall_scoreis normalised onto the same 0-10 scale as everything else — but only when it has a score and the report is released; otherwise the key is absent.evaluationholds the AI evaluation for the submission — the interview evaluation on an interview set, and the CV evaluation on a CV set (assessment_type: "cv_evaluation").evaluation_cvis the separate CV evaluation attached to an interview set. Either can benull.- Inside either object, a key you do not see is absent, not
null—competenciesis omitted entirely when there are no per-criterion rows, so testif (evaluation?.competencies)rather than comparing with[]. Both objects may also carryrating_status: "cannot_evaluate", meaning the AI ran but could not rate the attempt. competencies[].nameis thetitleof eachiai_requirements[]item you sent. Name them whatever you like; they come back under those names. A requirement you sent with an emptytitleis left out of the evaluation altogether.
5.4 Ask for the full detail: ?include=
Two extras are opt-in. Combine them with a comma. rubric is opt-in because it costs an extra column read
most callers never need; justifications is opt-in because this same response feeds our in-product
assistant, and written reasoning about a candidate should be something you ask for.
| Value | Adds |
|---|---|
justifications |
The AI's written reasoning for each criterion, as competencies[].justification. |
rubric |
Up to 13 communication and cognitive dimensions, each with a score out of 5 and a written rationale. A dimension the evaluation did not produce is left out of the array. |
curl -s -H 'X-API-Key: fl_live_abcd12345.n8G3zPNw9gWkK1Kj' \
'https://apis.flowmingo.ai/company/integration/hiring/submissions/{submission_id}/detail/v1?include=justifications,rubric'
{
"evaluation": {
"competencies": [
{
"name": "Applied AI/ML Engineering",
"rating": 9,
"justification": "Described building and deploying two retrieval pipelines, including how they chunked and evaluated them."
}
]
},
"rubric": [
{ "key": "grammar", "label": "Grammar", "group": "communication", "score": 4, "score_max": 5, "rationale": "..." },
{ "key": "logical_reasoning", "label": "Logical Reasoning", "group": "cognitive", "score": 3.5, "score_max": 5, "rationale": "..." },
{ "key": "clarity", "label": "Clarity", "group": "cognitive", "score": null, "score_max": 5, "score_status": "cannot_evaluate", "rationale": "Too little sustained speech to judge." }
]
}
Two scales in one response. Your criteria (
competencies[].rating) andoverall_scoreare out of 10. The rubric is out of 5 — every rubric row repeatsscore_maxso the two cannot be confused.
The rubric's group is communication (Grammar, Comprehension, Fluency, Vocabulary, Coherence) or
cognitive (Logical Reasoning, Critical Thinking, Problem Solving, Big-picture thinking, Intellectual
Self-Awareness, Insightfulness, Clarity, Decision Making). It is the same rubric shown in the report inside
Flowmingo.
score is null whenever the stored value is not a number between 0 and 5. When the AI ran and explicitly
could not rate a dimension, that row also carries score_status: "cannot_evaluate" — so a null score with
no score_status simply means no usable rating was recorded. rationale can be null independently of the
score. rubric itself is null when the submission has none; it is produced for AI interviews.
An unrecognised include value returns 400 rather than being ignored, so a typo cannot look like "the
feature returned nothing".
5.5 When a score or evaluation is missing
A candidate may be given a window to improve their answers after finishing. While that window is open the
result is deliberately withheld, so you never store a score that is about to change: status reads 6
(retaking), score_status reads "not_scored" even though a score exists, and overall_score,
submission_at, evaluation and rubric come back null. status: 6 is what distinguishes a withheld
result from one that was never scored.
This is temporary and resolves itself. The interview.evaluation.update webhook is held back for the same
reason and re-sent by a job that runs every 10 minutes, so it arrives shortly after the window closes. Read
the detail endpoint when that webhook arrives rather than polling straight after an interview ends. See the webhook guide.
6. Troubleshooting
401 and 403 mean different things, and the message usually tells you which problem you have. Checks run in order — key present, well-formed, recognised, unexpired, correctly scoped — and the first failure is the one reported, so an expired key reports expiry even if something else is also wrong:
| Status | Message | What it means |
|---|---|---|
| 401 | API key is required |
No key reached us. The header name is wrong, or you sent Authorization: Bearer — see section 2.6. Generating a new key will not help. |
| 401 | Invalid API key format |
The key is malformed: it must be the full prefix.secret value, including the .. |
| 401 | Invalid API key |
The key is not recognised, or it was deleted or deactivated. A masked value copied from the key list (fl_live_abcd12345.****) also lands here — it is well-formed, but the secret is not the real one. |
| 403 | API key expired |
The key passed its expiry date. |
| 403 | API key missing required scope: <scope> |
The key is valid but lacks that scope (create_set to create sets, invite_candidates to invite, read_hiring to read results, read_candidates for candidate names and emails). |
There is no separate "enable API access" setting on an account: a key that exists, is active, has not expired and carries the right scope works.
- 404 project/interview set: The UUID is invalid for your organization or the record is deleted.
- Validation errors (invite): Ensure
candidatesis non-empty and each candidate has enough data for your desired flow (invite vs CV-entry queue). - Validation errors (set):
titleis required.question_modeandquestion_typeare optional — omit them and you getinterview/text— but if you DO send one it must be a valid value (pre_screening|interview, andtext|checkbox|radio|file). Also check max-length limits on string fields, and thatproject_id(if sent) is a valid UUID owned by your organization. - Evaluation criteria look wrong or missing: check
cfg_importance_idagainst the four IDs in 3.2.2 — a value outside that set is stored as244(Good To Have) rather than rejected. - Duplicate ATS IDs in one payload: Ensure
candidates[].ats_candidate_idvalues are unique per request.