Tracy at Qode
Back to help

Tracy at Qode API for Zapier

The sign-in and the API calls the Tracy at Qode app in Zapier makes, and the call that creates an interview: what each one is called with and what it answers. Looking for the steps to connect it? Read the Zapier guide.

Base URL and conventions

Every address on this page starts with the address of this site:

https://tracyinterview.ai
  • Calls to the API and their answers are JSON. The token call is the one exception: it sends a form.
  • The Zapier app adds the header X-Tracy-Client: zapier to every request, so a host can see which of their calls came from Zapier.
  • Times are ISO 8601 in UTC, such as 2026-10-13T09:30:00.000Z.

Sign in

Tracy at Qode signs a host in with the OAuth 2.0 authorization code flow and PKCE. Zapier opens the authorize address in a window, the host signs in to Tracy at Qode and clicks Allow, and Zapier trades the code it is sent back for tokens at the token address.

  • PKCE is required, and the one method accepted is S256.
  • The Zapier app is a client that Tracy at Qode registers by hand, with a client ID, a client secret and the redirect address Zapier shows for the app. It sends the ID and the secret to the token address.
  • An access token lasts 1 hour (expires_in is 3600).
  • A refresh token lasts 60 days and rotates: every refresh answers with a new refresh token, which starts its own 60 days, and the one that was sent cannot be used again. Sending a used refresh token a second time ends the connection, and the host signs in again.
  • The one scope is tracy. Every connection gets it, so the app asks for none.

Authorize

Send the host's browser here. Tracy at Qode asks them to sign in if they are signed out, shows what Zapier asks for, and sends the browser on to the redirect address when they choose.

GET https://tracyinterview.ai/oauth/authorize

Query parameters

  • client_id: the client ID Tracy at Qode gave Zapier.
  • redirect_uri: the callback address Zapier shows for the app. It must match one registered for the client exactly.
  • response_type: always code.
  • state: a value Zapier chooses. It comes back unchanged on the redirect.
  • code_challenge: the unpadded base64url SHA-256 of the code verifier.
  • code_challenge_method: always S256.

Answer: a redirect

There is no JSON answer. The browser is sent to redirect_uri with code, state and iss (this site's address) added to it. The code works once and for 10 minutes. If the host chooses Deny, or the request is refused after the redirect address is known, the redirect carries error and error_description instead of a code.

REDIRECT_URI?code=CODE&state=STATE&iss=https://tracyinterview.ai

Get tokens

Trades the code from the redirect for an access token and a refresh token.

POST https://tracyinterview.ai/oauth/token

Headers

  • Content-Type: application/x-www-form-urlencoded. A JSON body is refused.

Form fields

  • grant_type: authorization_code.
  • code: the code from the redirect.
  • redirect_uri: the same address the authorize request used.
  • code_verifier: the secret the code_challenge was made from: 43 to 128 characters of A-Z, a-z, 0-9, -, ., _ and ~.
  • client_id: the client ID.
  • client_secret: the client secret.

Answer: 200

The access token goes in the Authorization header of every call below. Keep the refresh token: it is the only way to get the next access token without a new sign-in.

{
  "access_token": "the access token",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "the refresh token",
  "scope": "tracy"
}

Refresh the access token

The same address as above, with a different grant. Zapier calls it when an access token has expired, and runs the step again with the new one.

POST https://tracyinterview.ai/oauth/token

Headers

  • Content-Type: application/x-www-form-urlencoded.

Form fields

  • grant_type: refresh_token.
  • refresh_token: the newest refresh token.
  • client_id: the client ID.
  • client_secret: the client secret.

Answer: 200

The same shape as Get tokens. Store the new refresh_token in place of the one you sent.

{
  "access_token": "the access token",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "the refresh token",
  "scope": "tracy"
}

Calls with the access token

The calls below act for the host who signed in, and only on their own account. Send the access token in a header on each one:

  • Authorization: Bearer followed by the access token.
  • X-Tracy-Client: zapier, as in the conventions above.

An expired, revoked or missing token is answered with 401 and the error unauthorized: refresh the access token and send the call again.

List events

What the Zapier triggers poll: the account's newest interview events, newest first. Each trigger asks for one type.

GET https://tracyinterview.ai/api/events

Query parameters

  • type: optional. One of invite.opened, interview.started, interview.completed, interview.incomplete and result.ready. Left out, events of every type are listed.
  • limit: optional. A whole number from 1 to 100, 50 when left out. The triggers ask for 100.

Answer: 200

An object with an events list, which is empty when there is nothing yet. Each event has a stable id. resultUrl is null for events with no results page, and completionState is set only on result.ready. A type or limit that is not allowed is answered with 400 and the error invalid_query.

{
  "events": [
    {
      "id": "evSample0000000001",
      "type": "interview.completed",
      "label": "Interview completed",
      "createdAt": "2026-10-13T09:30:00.000Z",
      "interview": {
        "id": "inSample0000000001",
        "title": "Customer discovery call"
      },
      "participant": {
        "id": "paSample0000000001",
        "name": "Ada Lovelace",
        "email": "ada@example.com",
        "externalRef": "crm-1042"
      },
      "resultId": "reSample0000000001",
      "interviewUrl": "https://tracyinterview.ai/interviews/inSample0000000001",
      "resultUrl": "https://tracyinterview.ai/participants/paSample0000000001/results",
      "completionState": null
    }
  ]
}

List interviews

Fills the interview dropdown of the Invite Participant action: the account's interviews, most recently updated first. It takes no parameters.

GET https://tracyinterview.ai/api/interviews

Answer: 200

A list of interviews, empty when the account has none. The action uses id and title.

[
  {
    "id": "inSample0000000001",
    "title": "Customer discovery call",
    "purpose": "Learn how customers pick a supplier.",
    "templateName": "Customer discovery",
    "mode": "video",
    "language": "en",
    "state": "Live",
    "participants": 12,
    "completed": 7,
    "openLinkOn": false,
    "questionCount": 8,
    "notSent": false,
    "openLink": {
      "enabled": false,
      "joins": 0,
      "joinCap": 100,
      "state": "Off",
      "expiresAt": "2026-10-27T09:30:00.000Z"
    },
    "updatedAt": "2026-10-13T09:30:00.000Z"
  }
]

Create an interview

Makes an interview from who the host is interviewing and what they want to find out. Tracy drafts its title, questions and result fields, unless the call brings a setup of its own.

POST https://tracyinterview.ai/api/interviews

Headers

  • Content-Type: application/json.
  • Idempotency-Key: optional. A call repeated with the same key is not run twice: the first answer is kept for 24 hours and sent again.

JSON body

  • participants: required. Who is being interviewed, up to 500 characters.
  • goal: required. What the host wants to find out, up to 2000 characters.
  • context: optional. Anything else Tracy should know, such as a job description, up to 20000 characters.
  • ref: optional. Your own reference for the interview, up to 200 characters. It comes back as externalRef.
  • mode: video or audio: how the participant joins. Left out: video.
  • language: the language the interview is held in: en, de, es, fr, id, ja, ko, nl or vi. Left out: en.
  • cheatingOn: optional, true or false: whether the interview starts with its integrity signals on. Left out: true. Only a video interview is checked, whatever this says. Anything that is not true or false is answered with 400 and invalid_request. The host can change it later.
  • setup: optional. A setup the host has already reviewed: title, questions and resultFields, and optionally template, intro and outro. The interview is made from it as it is, and Tracy does not draft one.

Example body

{
  "participants": "Candidates for a Customer Support Lead role",
  "goal": "Whether they have led a small team and stay calm with upset customers",
  "ref": "ats-42",
  "mode": "video",
  "language": "en",
  "cheatingOn": false
}

Answer: 201

The new interview, which starts at version 1. The example shows the fields named above; the answer also carries the questions, the result fields and the open link.

{
  "id": "inSample0000000002",
  "title": "Customer Support Lead screen",
  "purpose": {
    "participants": "Candidates for a Customer Support Lead role",
    "goal": "Whether they have led a small team and stay calm with upset customers",
    "context": null
  },
  "version": 1,
  "mode": "video",
  "language": "en",
  "emailsOn": true,
  "cheatingOn": false,
  "externalRef": "ats-42"
}

Invite a participant

Invites one person to an interview and has Tracy at Qode email them their personal link. Replace :id with the id of an interview from the list above.

POST https://tracyinterview.ai/api/interviews/:id/participants

Headers

  • Content-Type: application/json.
  • Idempotency-Key: optional, and the Zapier app does not send one. A call repeated with the same key is not run twice: the first answer is kept for 24 hours and sent again.

JSON body

  • people: required. One email address, or Name <email>. Without a name, Tracy at Qode uses the start of the address. Further lines invite further people, up to 200 in a call; the Zapier app sends one.
  • ref: optional. Your own reference for the person, up to 200 characters. It is kept only when the call invites one person. It comes back as externalRef on the person, and on every event for them.

Example body

{
  "people": "Ada Lovelace <ada@example.com>",
  "ref": "crm-1042"
}

Answer: 201

participants has the person that was invited. When the interview is paused or the credits are used up, held is true and heldReason says which (paused or credits): the person is saved, and no email has gone out yet. emailed is true when the invite email was accepted for sending. An interview that is not yours, or does not exist, is answered with 404 and interview_not_found; one that has not been given a title yet, with 409 and title_not_set.

{
  "participants": [
    {
      "id": "paSample0000000001",
      "name": "Ada Lovelace",
      "email": "ada@example.com",
      "via": "invite",
      "link": "https://tracyinterview.ai/i/customer-discovery-k7m2xq?candidateId=paSample0000000001",
      "status": "invited",
      "statusLabel": "Invited",
      "paused": false,
      "expiredWhilePaused": false,
      "completionState": null,
      "sessions": 0,
      "emailStatus": "sent",
      "expiresAt": "2026-10-27T09:30:00.000Z",
      "version": 1,
      "externalRef": "crm-1042",
      "source": "zapier",
      "sessionMinutes": null
    }
  ],
  "held": false,
  "emailed": true
}

Read the host's name

The one thing the token can tell about who signed in. The Zapier app reads it after connecting and shows it as the connection's name.

GET https://tracyinterview.ai/api/me/name

Answer: 200

name is null when the host has not set one.

{
  "name": "Ada Lovelace"
}

Errors

A call to the API that fails is answered with a status of 400 or more and this JSON, where code is stable and safe to test for, and message is a sentence for the person at the keyboard:

{
  "error": {
    "code": "unauthorized",
    "message": "Sign in, or send a valid API key, to use this."
  }
}
  • 400: the call is malformed: invalid_query, invalid_json, invalid_request or invalid_people.
  • 401: unauthorized: the access token is missing, expired or revoked.
  • 404: interview_not_found.
  • 409: title_not_set.
  • 500: internal_error: something went wrong on the Tracy at Qode side. Try the call again.

The sign-in addresses are the exception. They answer in the shape OAuth 2.0 defines (RFC 6749), with an error status, so a client that already speaks OAuth can read it. invalid_grant means the code or the refresh token is expired, already used or revoked, and only a new sign-in helps.

{
  "error": "invalid_grant",
  "error_description": "The grant is invalid, expired or already used."
}
HomePricingHelpAdd to ClaudeAdd to ChatGPTSupportPrivacyTerms

Tracy at Qode runs AI video and audio interviews.