Interviews
Invite a candidate
/positions/{id}/invitesCreates an interview for one candidate and returns its inviteUrl. The position must be open
and have at least one question.
No credit is used yet. The API only checks that your organization has an active subscription with interview credits left. One credit is used when the candidate starts the interview.
Email. With sendEmail: true (the default), Videcoot emails the invite to the candidate.
With sendEmail: false, send inviteUrl to the candidate yourself. If the email fails, the invite
is still created and emailSent is false.
Idempotency (always send externalId). externalId is your ID for the candidate or application,
and it's unique per position.
- If an interview with the same
externalIdalready exists on this position (and hasn't been cancelled), it is returned with 200, whatever its status, includingexpiredandcompleted. The request body isn't compared, and no email is sent. - Otherwise a new invite is created and returned with 201.
- To re-invite after an invite expired,
DELETE /interviews/{id}and thenPOSTagain.
Same email. A candidate email can have only one live (created or in_progress) interview per position.
- If that live interview is an API invite with the same
externalId(or either has noexternalId), it is returned with 200. - Otherwise the API returns 409 (
Candidate already has an active interview for this position). - A candidate can have at most 3 interviews per position (cancelled ones don't count). The fourth attempt returns 409.
Path parameters
idstringrequiredpattern ^[0-9a-fA-F]{24}$A 24-character hex ID.
Example:
"66f3a1c2e4b0a1d2c3e4f530"
Request body InviteRequestrequired
candidateobjectrequiredexternalIdstringmin length 1max length 200Your ID for this candidate or application. Unique per position; it makes retries safe (see the operation description). Strongly recommended.
sendEmailbooleantrue: Videcoot emails the invite.false: you sendinviteUrlyourself.Default:
trueexpiresInDaysintegermin 1max 90Days until the invite link expires if no answer has been recorded by then.
Default:
14
Responses
201A new invite was created.
A new invite was created.
Body InviteResponseEnvelope
dataInviteResponserequiredidstringrequiredpattern ^[0-9a-fA-F]{24}$A 24-character hex ID.
positionIdstringrequiredpattern ^[0-9a-fA-F]{24}$A 24-character hex ID.
externalIdstring | nullrequiredYour ID for this candidate or application, as sent on the invite.
sourcestringrequiredapifor invites created through this API;dashboardfor candidates who applied through a position link.Values:
"dashboard", "api"statusstringrequiredcreated: invited; no answer recorded yet (the candidate may have opened the link).in_progress: at least one answer recorded.completed: all questions answered.abandoned: the candidate left without finishing.expired: the interview expired, or the invite passedinviteExpiresAtwhile stillcreated.
Values:
"created", "in_progress", "completed", "abandoned", "expired"approvalStatusstringrequiredThe reviewer's decision in the Videcoot dashboard.
Values:
"pending", "approved", "rejected"candidateCandidaterequiredinviteUrlstring (uri) | nullrequiredThe candidate's personal interview link.
nullfor interviews not created by the API.inviteExpiresAtstring (date-time) | nullrequiredAfter this time an invite still in
created(no answers recorded) reportsstatusexpired.nullfor interviews not created by the API.answeredQuestionsintegerrequiredmin 0completedAtstring (date-time) | nullrequiredanalysisStatusstringrequiredValues:
"not_requested", "pending", "processing", "completed", "failed"createdAtstring (date-time) | nullrequiredupdatedAtstring (date-time) | nullrequiredemailSentbooleanrequiredtrueif Videcoot emailed the invite in this request. Alwaysfalsewhen an existing interview is returned (200).
timestampinteger (int64)requiredServer time in milliseconds since the Unix epoch.
{
"data": {
"id": "66f4b7d9a1c2e3f4a5b6c701",
"positionId": "66f3a1c2e4b0a1d2c3e4f530",
"externalId": "APP-2026-00042",
"source": "api",
"status": "created",
"approvalStatus": "pending",
"candidate": {
"firstName": "Nok",
"lastName": "Srisuk",
"email": "nok.srisuk@example.com",
"phoneNumber": "812345678",
"countryCode": "+66"
},
"inviteUrl": "https://app.videcoot.com/interviews/positions/66f3a1c2e4b0a1d2c3e4f530?invite=a5880531dab9825f995ac073d405b57d240199fd0487609962899c34e031d4e5",
"inviteExpiresAt": "2026-10-09T10:00:00.000Z",
"answeredQuestions": 0,
"completedAt": null,
"analysisStatus": "not_requested",
"createdAt": "2026-09-25T10:00:00.000Z",
"updatedAt": "2026-09-25T10:00:00.000Z",
"emailSent": true
},
"timestamp": 1790330400000
}200An existing interview was returned instead of creating a new one: the same externalId was
sent before, or the candidate already has a live API invite for this position. emailSent is
always false.
An existing interview was returned instead of creating a new one: the same externalId was
sent before, or the candidate already has a live API invite for this position. emailSent is
always false.
Body InviteResponseEnvelope
dataInviteResponserequiredidstringrequiredpattern ^[0-9a-fA-F]{24}$A 24-character hex ID.
positionIdstringrequiredpattern ^[0-9a-fA-F]{24}$A 24-character hex ID.
externalIdstring | nullrequiredYour ID for this candidate or application, as sent on the invite.
sourcestringrequiredapifor invites created through this API;dashboardfor candidates who applied through a position link.Values:
"dashboard", "api"statusstringrequiredcreated: invited; no answer recorded yet (the candidate may have opened the link).in_progress: at least one answer recorded.completed: all questions answered.abandoned: the candidate left without finishing.expired: the interview expired, or the invite passedinviteExpiresAtwhile stillcreated.
Values:
"created", "in_progress", "completed", "abandoned", "expired"approvalStatusstringrequiredThe reviewer's decision in the Videcoot dashboard.
Values:
"pending", "approved", "rejected"candidateCandidaterequiredinviteUrlstring (uri) | nullrequiredThe candidate's personal interview link.
nullfor interviews not created by the API.inviteExpiresAtstring (date-time) | nullrequiredAfter this time an invite still in
created(no answers recorded) reportsstatusexpired.nullfor interviews not created by the API.answeredQuestionsintegerrequiredmin 0completedAtstring (date-time) | nullrequiredanalysisStatusstringrequiredValues:
"not_requested", "pending", "processing", "completed", "failed"createdAtstring (date-time) | nullrequiredupdatedAtstring (date-time) | nullrequiredemailSentbooleanrequiredtrueif Videcoot emailed the invite in this request. Alwaysfalsewhen an existing interview is returned (200).
timestampinteger (int64)requiredServer time in milliseconds since the Unix epoch.
{
"data": {
"id": "66f4b7d9a1c2e3f4a5b6c701",
"positionId": "66f3a1c2e4b0a1d2c3e4f530",
"externalId": "APP-2026-00042",
"source": "api",
"status": "in_progress",
"approvalStatus": "pending",
"candidate": {
"firstName": "Nok",
"lastName": "Srisuk",
"email": "nok.srisuk@example.com",
"phoneNumber": "812345678",
"countryCode": "+66"
},
"inviteUrl": "https://app.videcoot.com/interviews/positions/66f3a1c2e4b0a1d2c3e4f530?invite=a5880531dab9825f995ac073d405b57d240199fd0487609962899c34e031d4e5",
"inviteExpiresAt": "2026-10-09T10:00:00.000Z",
"answeredQuestions": 1,
"completedAt": null,
"analysisStatus": "not_requested",
"createdAt": "2026-09-25T10:00:00.000Z",
"updatedAt": "2026-09-26T02:14:51.000Z",
"emailSent": false
},
"timestamp": 1790381691000
}400Invalid input, the position isn't open (Position is not open for interviews), or the position
has no questions (No questions found for this position).
Invalid input, the position isn't open (Position is not open for interviews), or the position
has no questions (No questions found for this position).
Body Error
successbooleanrequiredValues:
falsemessagestringrequiredA human-readable error message.
timestampinteger (int64)requiredServer time in milliseconds since the Unix epoch.
{
"success": false,
"message": "expiresInDays must be between 1 and 90",
"timestamp": 1790330400000
}401The API key is missing, malformed, invalid or revoked.
The API key is missing, malformed, invalid or revoked.
Body Error
successbooleanrequiredValues:
falsemessagestringrequiredA human-readable error message.
timestampinteger (int64)requiredServer time in milliseconds since the Unix epoch.
{
"success": false,
"message": "API key is required. Send it as 'Authorization: Bearer <key>'",
"timestamp": 1790330400000
}403API access is disabled for your organization (any endpoint), or, when creating an invite, your
organization has no active subscription or no interview credits left.
API access is disabled for your organization (any endpoint), or, when creating an invite, your organization has no active subscription or no interview credits left.
Body Error
successbooleanrequiredValues:
falsemessagestringrequiredA human-readable error message.
timestampinteger (int64)requiredServer time in milliseconds since the Unix epoch.
{
"success": false,
"message": "API access is not enabled for this organization",
"timestamp": 1790330400000
}404The resource doesn't exist, was cancelled, or belongs to another organization. The API never
returns 403 for another organization's IDs.
The resource doesn't exist, was cancelled, or belongs to another organization. The API never returns 403 for another organization's IDs.
Body Error
successbooleanrequiredValues:
falsemessagestringrequiredA human-readable error message.
timestampinteger (int64)requiredServer time in milliseconds since the Unix epoch.
{
"success": false,
"message": "Interview not found",
"timestamp": 1790330400000
}409The request conflicts with the current state of the resource.
The request conflicts with the current state of the resource.
Body Error
successbooleanrequiredValues:
falsemessagestringrequiredA human-readable error message.
timestampinteger (int64)requiredServer time in milliseconds since the Unix epoch.
{
"success": false,
"message": "Candidate already has an active interview for this position",
"timestamp": 1790330400000
}429Too many requests. Wait for Retry-After seconds (or the reset value in RateLimit), then retry.
Too many requests. Wait for Retry-After seconds (or the reset value in RateLimit), then retry.
Headers
RateLimitstringDraft-7 rate limit state, e.g.
limit=120, remaining=0, reset=42(reset is in seconds).RateLimit-PolicystringDraft-7 rate limit policy: the limit and the window in seconds.
Retry-AfterintegerSeconds to wait before retrying.
Body Error
successbooleanrequiredValues:
falsemessagestringrequiredA human-readable error message.
timestampinteger (int64)requiredServer time in milliseconds since the Unix epoch.
{
"success": false,
"message": "Too many requests. Retry after the number of seconds in the RateLimit header.",
"timestamp": 1790330400000
}