Skip to content

Applications

Submit candidates to jobs and track their progress through the hiring pipeline.

Submit an application

POST /api/v1/external/applications

Required headers

HeaderValue
AuthorizationBearer YOUR_API_KEY_HERE
Idempotency-KeyUUID v4 (unique per submission attempt)
Content-Typeapplication/json

Request body

json
{
  "jobId": "job_12345",
  "candidate": {
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane.doe@example.com",
    "phone": "+15551234567"
  },
  "consentToContact": true,
  "source": "indeed",
  "sourceApplicationId": "your-internal-id-123",
  "applicationFormAnswers": {
    "drivers_license": true
  },
  "resumeUrl": "https://your-cdn.com/resumes/jane-doe.pdf"
}

Field descriptions

FieldRequiredDescription
jobIdYesJob to apply to (from GET /jobs response)
candidate.firstNameYes1-100 characters
candidate.lastNameYes1-100 characters
candidate.emailYesValid email address
candidate.phoneYesE.164 format recommended (e.g. +15551234567). We normalize
consentToContactYesMust be true. Applicant consents to SMS/email outreach
sourceNoUpstream channel (e.g. indeed, linkedin, facebook)
sourceApplicationIdNoYour internal ID for this application (max 128 chars). Returned in webhooks
applicationFormAnswersNoKey-value answers matching the job's applicationFormSchema
resumeUrlNoWe fetch the file asynchronously; fetch failure is non-fatal

Response (201 Created)

json
{
  "id": "ej_app_789",
  "sourceApplicationId": "your-internal-id-123",
  "jobId": "job_12345",
  "status": "received",
  "createdAt": "2026-05-22T14:30:11Z"
}

Error responses

StatusCodeWhen
422IDEMPOTENCY_KEY_REQUIREDMissing Idempotency-Key header
422IDEMPOTENCY_KEY_REUSEDSame key, different request body bytes
422VALIDATION_ERRORField validation failed (see details.errors)
404JOB_NOT_FOUNDJob doesn't exist or you lack access
409JOB_CLOSEDJob exists but is no longer accepting applications
403ACCESS_REVOKEDYour access to this company was revoked
409DUPLICATE_SOURCE_APPLICATION_IDSame sourceApplicationId already submitted
429RATE_LIMITEDPer-company rate limit exceeded

Idempotency

The Idempotency-Key header ensures at-most-once submission. If you retry a request with the same key AND same request body bytes, you get the cached 201 response. If you use the same key with different body bytes, you get 422 IDEMPOTENCY_KEY_REUSED.

Keys expire after 24 hours.

TIP

"Same request body" means identical raw bytes — not semantically equivalent JSON. {"a":1} and { "a": 1 } are different bytes and different idempotency signatures.

Examples

bash
curl -X POST "https://api.employjoy.ai/api/v1/external/applications" \
  -H "Authorization: Bearer YOUR_API_KEY_HERE" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "jobId": "job_12345",
    "candidate": {
      "firstName": "Jane",
      "lastName": "Doe",
      "email": "jane.doe@example.com",
      "phone": "+15551234567"
    },
    "consentToContact": true,
    "source": "indeed",
    "sourceApplicationId": "your-internal-id-123"
  }'
javascript
import { randomUUID } from 'crypto';

const response = await fetch('https://api.employjoy.ai/api/v1/external/applications', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY_HERE',
    'Idempotency-Key': randomUUID(),
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    jobId: 'job_12345',
    candidate: {
      firstName: 'Jane',
      lastName: 'Doe',
      email: 'jane.doe@example.com',
      phone: '+15551234567',
    },
    consentToContact: true,
    source: 'indeed',
    sourceApplicationId: 'your-internal-id-123',
  }),
});

const result = await response.json();
console.log(result.id); // "ej_app_789"
python
import requests
import uuid

response = requests.post(
    'https://api.employjoy.ai/api/v1/external/applications',
    headers={
        'Authorization': 'Bearer YOUR_API_KEY_HERE',
        'Idempotency-Key': str(uuid.uuid4()),
        'Content-Type': 'application/json',
    },
    json={
        'jobId': 'job_12345',
        'candidate': {
            'firstName': 'Jane',
            'lastName': 'Doe',
            'email': 'jane.doe@example.com',
            'phone': '+15551234567',
        },
        'consentToContact': True,
        'source': 'indeed',
        'sourceApplicationId': 'your-internal-id-123',
    },
)
print(response.json()['id'])  # "ej_app_789"

Check application status

GET /api/v1/external/applications/:id

Response (200 OK)

json
{
  "id": "ej_app_789",
  "sourceApplicationId": "your-internal-id-123",
  "jobId": "job_12345",
  "status": "in_progress",
  "currentStage": "Video Interview",
  "submittedAt": "2026-05-22T14:30:11Z",
  "lastStatusChangeAt": "2026-05-23T09:14:22Z"
}

Status values

StatusMeaning
receivedApplication submitted, initial outreach pending
in_progressActively being evaluated (video interview, review, etc.)
acceptedCandidate hired
rejectedCandidate not selected

Current stage labels

LabelInternal stage
Initial OutreachJust submitted, pre-SMS
Video InterviewAwaiting or in video interview
Under ReviewVideo submitted, scoring in progress
Final ReviewRating, committee, or in-person interview
HiredAccepted
ClosedRejected

List your applications

GET /api/v1/external/applications

Same response shape wrapped in { data: [...], next_cursor }.

Query parameters

ParameterTypeDescription
updated_sinceISO-8601Filter by update time
cursorstringOpaque pagination cursor
limitintegerDefault 50, max 200
company_idstringFilter to one company
statusstringOne of: received, in_progress, accepted, rejected

You can only see applications that your key submitted (sourcePartnerId matches). Read access persists even if company access is later revoked.

Partner API v1