Interviews

Invite a candidate

POST/positions/{id}/invites

Creates 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 externalId already exists on this position (and hasn't been cancelled), it is returned with 200, whatever its status, including expired and completed. 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 then POST again.

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 no externalId), 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

  • candidateobjectrequired
  • externalIdstringmin length 1max length 200

    Your ID for this candidate or application. Unique per position; it makes retries safe (see the operation description). Strongly recommended.

  • sendEmailboolean

    true: Videcoot emails the invite. false: you send inviteUrl yourself.

    Default: true

  • expiresInDaysintegermin 1max 90

    Days until the invite link expires if no answer has been recorded by then.

    Default: 14

Responses

201

A new invite was created.

Body InviteResponseEnvelope

  • dataInviteResponserequired
    • idstringrequiredpattern ^[0-9a-fA-F]{24}$

      A 24-character hex ID.

    • positionIdstringrequiredpattern ^[0-9a-fA-F]{24}$

      A 24-character hex ID.

    • externalIdstring | nullrequired

      Your ID for this candidate or application, as sent on the invite.

    • sourcestringrequired

      api for invites created through this API; dashboard for candidates who applied through a position link.

      Values: "dashboard", "api"

    • statusstringrequired
      • created: 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 passed inviteExpiresAt while still created.

      Values: "created", "in_progress", "completed", "abandoned", "expired"

    • approvalStatusstringrequired

      The reviewer's decision in the Videcoot dashboard.

      Values: "pending", "approved", "rejected"

    • candidateCandidaterequired
    • inviteUrlstring (uri) | nullrequired

      The candidate's personal interview link. null for interviews not created by the API.

    • inviteExpiresAtstring (date-time) | nullrequired

      After this time an invite still in created (no answers recorded) reports status expired. null for interviews not created by the API.

    • answeredQuestionsintegerrequiredmin 0
    • completedAtstring (date-time) | nullrequired
    • analysisStatusstringrequired

      Values: "not_requested", "pending", "processing", "completed", "failed"

    • createdAtstring (date-time) | nullrequired
    • updatedAtstring (date-time) | nullrequired
    • emailSentbooleanrequired

      true if Videcoot emailed the invite in this request. Always false when an existing interview is returned (200).

  • timestampinteger (int64)required

    Server time in milliseconds since the Unix epoch.

Example
{
  "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
}
200

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

  • dataInviteResponserequired
    • idstringrequiredpattern ^[0-9a-fA-F]{24}$

      A 24-character hex ID.

    • positionIdstringrequiredpattern ^[0-9a-fA-F]{24}$

      A 24-character hex ID.

    • externalIdstring | nullrequired

      Your ID for this candidate or application, as sent on the invite.

    • sourcestringrequired

      api for invites created through this API; dashboard for candidates who applied through a position link.

      Values: "dashboard", "api"

    • statusstringrequired
      • created: 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 passed inviteExpiresAt while still created.

      Values: "created", "in_progress", "completed", "abandoned", "expired"

    • approvalStatusstringrequired

      The reviewer's decision in the Videcoot dashboard.

      Values: "pending", "approved", "rejected"

    • candidateCandidaterequired
    • inviteUrlstring (uri) | nullrequired

      The candidate's personal interview link. null for interviews not created by the API.

    • inviteExpiresAtstring (date-time) | nullrequired

      After this time an invite still in created (no answers recorded) reports status expired. null for interviews not created by the API.

    • answeredQuestionsintegerrequiredmin 0
    • completedAtstring (date-time) | nullrequired
    • analysisStatusstringrequired

      Values: "not_requested", "pending", "processing", "completed", "failed"

    • createdAtstring (date-time) | nullrequired
    • updatedAtstring (date-time) | nullrequired
    • emailSentbooleanrequired

      true if Videcoot emailed the invite in this request. Always false when an existing interview is returned (200).

  • timestampinteger (int64)required

    Server time in milliseconds since the Unix epoch.

Example
{
  "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
}
400

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

  • successbooleanrequired

    Values: false

  • messagestringrequired

    A human-readable error message.

  • timestampinteger (int64)required

    Server time in milliseconds since the Unix epoch.

{
  "success": false,
  "message": "expiresInDays must be between 1 and 90",
  "timestamp": 1790330400000
}
401

The API key is missing, malformed, invalid or revoked.

Body Error

  • successbooleanrequired

    Values: false

  • messagestringrequired

    A human-readable error message.

  • timestampinteger (int64)required

    Server time in milliseconds since the Unix epoch.

{
  "success": false,
  "message": "API key is required. Send it as 'Authorization: Bearer <key>'",
  "timestamp": 1790330400000
}
403

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

  • successbooleanrequired

    Values: false

  • messagestringrequired

    A human-readable error message.

  • timestampinteger (int64)required

    Server time in milliseconds since the Unix epoch.

{
  "success": false,
  "message": "API access is not enabled for this organization",
  "timestamp": 1790330400000
}
404

The resource doesn't exist, was cancelled, or belongs to another organization. The API never returns 403 for another organization's IDs.

Body Error

  • successbooleanrequired

    Values: false

  • messagestringrequired

    A human-readable error message.

  • timestampinteger (int64)required

    Server time in milliseconds since the Unix epoch.

Example
{
  "success": false,
  "message": "Interview not found",
  "timestamp": 1790330400000
}
409

The request conflicts with the current state of the resource.

Body Error

  • successbooleanrequired

    Values: false

  • messagestringrequired

    A human-readable error message.

  • timestampinteger (int64)required

    Server time in milliseconds since the Unix epoch.

{
  "success": false,
  "message": "Candidate already has an active interview for this position",
  "timestamp": 1790330400000
}
429

Too many requests. Wait for Retry-After seconds (or the reset value in RateLimit), then retry.

Headers

  • RateLimitstring

    Draft-7 rate limit state, e.g. limit=120, remaining=0, reset=42 (reset is in seconds).

  • RateLimit-Policystring

    Draft-7 rate limit policy: the limit and the window in seconds.

  • Retry-Afterinteger

    Seconds to wait before retrying.

Body Error

  • successbooleanrequired

    Values: false

  • messagestringrequired

    A human-readable error message.

  • timestampinteger (int64)required

    Server time in milliseconds since the Unix epoch.

Example
{
  "success": false,
  "message": "Too many requests. Retry after the number of seconds in the RateLimit header.",
  "timestamp": 1790330400000
}