Applications

An Application represents a candidate linked to a job in the pipeline — a row in candidate_jobs. The Applications API gives you direct control over creating, reading, updating, and deleting these relationships.

You can also apply candidates inline when creating or updating them via the Candidates API using the nested application object — both paths are fully equivalent and idempotent.

Idempotency

POST /applications is idempotent: if a candidate_id + job_id pair already exists for your organization, the response returns 200 with the existing application (and patches any provided fields). A genuinely new application returns 201. This makes it safe to replay ATS webhooks without creating duplicate records.

Side effects

  • First-apply email is sent once on the first successful apply only — either the interest outreach email or the video interview invite, depending on the job's direct_video_interview_on_apply setting. Retries do not trigger a second email.
  • Screening is not started automatically on apply. Use the Screenings API or the Dashboard to initiate it.
  • Pipeline timestamps — updating status triggers side effects: screening_started_at is set when status becomes screening; screening_completed_at when it becomes screened; closed_at for hired / rejected / Candidate joined.
  • DELETE — removes the candidate from the job pipeline. candidate_job_status_events and marketing_campaign_recipients are cascade-deleted. Screening sessions and video interviews are retained.
  • shortlisted_by_user_id is null when applied via API key (no associated user identity).

Pipeline statuses

Status strings are case-sensitive and must match your organization's configured pipeline statuses exactly (e.g. Applied, Shortlisted, screening). Sending an unrecognized status returns invalid_pipeline_status (400) with an allowed list. See Errors for full error codes.

Talent pool

Applying a talent-pool candidate whose profile has not yet been revealed returns talent_pool_reveal_required (403). Reveal the profile in the Dashboard first. In list and detail responses, candidate_email is null for masked profiles.

List query parameters (GET)

GET /api/v1/applications
FieldTypeRequiredDescription
candidate_idstring (uuid)NoFilter by candidate
job_idstring (uuid)NoFilter by job
statusstringNoExact pipeline status match
searchstringNoILIKE on candidate name, email, job title
limitintegerNoPage size (1–100, default 50)
offsetintegerNoSkip N records (default 0)

Request body — Create (POST)

POST /api/v1/applications
FieldTypeRequiredDescription
candidate_idstring (uuid)YesCandidate to apply (must belong to your org)
job_idstring (uuid)YesJob to apply to (must belong to your org)
statusstringNoPipeline status (default: Applied; must match org settings)
notesstring | nullNoRecruiter notes
match_scorenumber | nullNoYour own match score 0–100, kept as is (omit it and Talynce computes one)
metadataobjectNoCustom JSON (e.g. ATS application ID)

Request body — Update (PUT)

At least one field is required. Omitted fields are left unchanged.

PUT /api/v1/applications/{id}
FieldTypeRequiredDescription
statusstringNoNew pipeline status. Triggers timestamp side effects.
notesstring | nullNoUpdated recruiter notes
match_scorenumber | nullNoYour own match score 0–100, kept as is; null lets Talynce recompute it
metadataobjectNoShallow-merged with existing metadata

Request body — Bulk create (POST)

POST /api/v1/applications/bulk
FieldTypeRequiredDescription
job_idstring (uuid)YesJob to apply all candidates to
candidate_idsstring[] (uuid, max 100)YesCandidate UUIDs to apply
statusstringNoPipeline status for all (default: Applied)
notesstring | nullNoNotes applied to all candidates
match_scoresobjectNoPer-candidate scores: { candidate_id: score }

Response body

Application resource (data)
FieldTypeRequiredDescription
idstring (uuid)—Application id (candidate_jobs row)
candidate_idstring (uuid)—Candidate id
job_idstring (uuid)—Job id
organization_idstring (uuid)—Organization id
statusstring—Current pipeline status
screening_scorenumber | null—Aggregate screening score
match_scorenumber | null—Match score 0–100: computed by Talynce unless your integration supplied one
notesstring | null—Recruiter notes
shortlisted_by_user_idstring (uuid) | null—User who applied; null when applied via API key
shortlisted_atstring (ISO 8601 date-time)—When applied
screening_started_atstring (ISO 8601 date-time)—Screening started (set when status → screening)
screening_completed_atstring (ISO 8601 date-time)—Screening completed (set when status → screened)
status_datestring (ISO 8601 date-time)—When candidate entered current status
outreach_sent_atstring (ISO 8601 date-time)—Interest outreach email sent on first apply (null when job uses direct_video_interview_on_apply or org outreach disabled)
interest_clicked_atstring (ISO 8601 date-time)—Candidate confirmed interest
video_interview_invite_sent_atstring (ISO 8601 date-time)—Video interview invite email sent (set on direct apply, after interest confirmation, or public apply)
whatsapp_started_atstring (ISO 8601 date-time)—First WhatsApp message received
closed_atstring (ISO 8601 date-time)—Set when status is hired / rejected / Candidate joined
closed_by_user_idstring (uuid) | null—User who closed
metadataobject | null—Custom JSON metadata
created_atstring (ISO 8601 date-time)—Created
updated_atstring (ISO 8601 date-time)—Updated
Enriched fields (list and detail responses only)
FieldTypeRequiredDescription
job_titlestring | null—Job title (list and detail responses)
job_statusstring | null—Job status (list and detail responses)
candidate_full_namestring | null—Candidate name (list and detail responses)
candidate_emailstring | null—Candidate email; null if talent pool profile is masked (list and detail responses)
List meta object
FieldTypeRequiredDescription
meta.totalinteger—Total matching records
meta.limitinteger—Page size used
meta.offsetinteger—Offset used

Endpoints

GET/api/v1/applications

List applications for your organization. Supports filtering by candidate, job, status, and full-text search.

POST/api/v1/applications

Apply a candidate to a job. Returns 201 for new applications, 200 for existing (idempotent). On first apply, sends interest outreach email or video interview invite depending on the job's direct_video_interview_on_apply setting.

Example
{
  "candidate_id": "550e8400-e29b-41d4-a716-446655440000",
  "job_id": "660e8400-e29b-41d4-a716-446655440001",
  "status": "Applied",
  "notes": "Imported from ATS",
  "match_score": 85.5,
  "metadata": { "external_application_id": "ATS-123" }
}
GET/api/v1/applications/{id}

Get an application by its id, with enriched job and candidate fields.

PUT/api/v1/applications/{id}

Partially update an application. At least one field required.

Example
{
  "status": "screening",
  "notes": "Passed phone screen",
  "match_score": 92,
  "metadata": { "interview_round": 1 }
}
DELETE/api/v1/applications/{id}

Remove application. Returns 204 with no body. Screening sessions are not deleted.

POST/api/v1/applications/bulk

Apply up to 100 candidates to a single job. Per-item errors are reported without failing the whole batch.

Example
{
  "job_id": "660e8400-e29b-41d4-a716-446655440001",
  "candidate_ids": [
    "550e8400-e29b-41d4-a716-446655440000",
    "770e8400-e29b-41d4-a716-446655440002"
  ],
  "status": "Applied",
  "notes": "Batch import from legacy ATS",
  "match_scores": {
    "550e8400-e29b-41d4-a716-446655440000": 88.0
  }
}

Code examples

Check if already applied (GET)
# Check whether a candidate is already applied to a job
GET /api/v1/applications?candidate_id=550e8400-...&job_id=660e8400-...

# Returns [] if not applied, or [application] if applied — safe to retry
Full ATS workflow
# Full ATS workflow
# 1. Create or upsert a client
POST /api/v1/clients { "name": "Acme Corp" }

# 2. Create or upsert a job
POST /api/v1/jobs { "title": "Senior Engineer", ... }

# 3. Create or upsert a candidate (deduplicated by email)
POST /api/v1/candidates { "full_name": "Jane Doe", "email": "[email protected]", ... }

# 4. Apply the candidate to the job
POST /api/v1/applications { "candidate_id": "...", "job_id": "..." }
# → 201 on first apply (outreach or video invite per job direct_video_interview_on_apply setting)
# → 200 on retry (idempotent, no duplicate email)

# 5. Later, sync pipeline status from ATS
PUT /api/v1/applications/{id} { "status": "Shortlisted" }
Update pipeline status (PUT)
PUT /api/v1/applications/${id}
Authorization: Bearer tly_live_...

{
  "status": "screening",
  "notes": "Passed phone screen",
  "match_score": 92,
  "metadata": { "interview_round": 1 }
}
Bulk import candidates (POST)
POST /api/v1/applications/bulk
Authorization: Bearer tly_live_...

{
  "job_id": "660e8400-e29b-41d4-a716-446655440001",
  "candidate_ids": [
    "550e8400-e29b-41d4-a716-446655440000",
    "770e8400-e29b-41d4-a716-446655440002"
  ],
  "status": "Applied",
  "notes": "Batch import from legacy ATS",
  "match_scores": {
    "550e8400-e29b-41d4-a716-446655440000": 88.0
  }
}