Skip to main content

Submit Job Application

NL EN

Updated April 22, 2026

POST /api/v1/applications
curl -X POST \
  "https://app.recruitsome.com/api/v1/applications" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"
const response = await fetch('https://app.recruitsome.com/api/v1/applications', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Accept': 'application/json',
  },
});

const data = await response.json();
console.log(data);
use Illuminate\Support\Facades\Http;

$response = Http::withToken('YOUR_API_KEY')
    ->acceptJson()
    ->post('https://app.recruitsome.com/api/v1/applications');

$data = $response->json();

Submit Job Application

The application submission endpoint allows candidates to apply for published vacancies by submitting their information and supporting documents. This endpoint supports multiple document uploads, comprehensive candidate profiles, and tracking information.

Implementation Strategy

Warning

Critical Implementation Pattern: Always store applications locally before submission and implement retry logic. Network failures, rate limits, or temporary server issues should not result in lost applications.

Recommended Implementation Flow

graph TD

A[User Fills Form] --> B[Validate Locally]

B --> C[Store in Local Storage/DB]

C --> D[Submit to API]

D --> E{Response}

E -->|Success 201| F[Clear Local Storage]

E -->|Error 4xx/5xx| G[Keep in Queue]

G --> H[Retry with Backoff]

H --> D

  1. Validate input locally before submission
  2. Store application data in local storage or database
  3. Show status to user
  4. Submit to API with proper error handling
  5. On success (201): Clear local storage and show confirmation
  6. On failure: Keep in queue and implement retry with exponential backoff

Simple Example

Submit a basic application with just the required fields:

``bash cURL

curl -X POST https://app\.recruitsome\.com/api/v1/applications \

-H "Authorization: Bearer YOUR_API_KEY" \

-H "Content-Type: application/json" \

-d '{

"vacancy": "senior-software-engineer-amsterdam",

"candidate": {

"given_name": "John",

"family_name": "Doe",

"email": "[email protected]"

}

}'


javascript JavaScript

const applicationData = {

vacancy: "senior-software-engineer-amsterdam",

candidate: {

given_name: "John",

family_name: "Doe",

email: "[email protected]"

}

};

// Store locally first

localStorage.setItem('pending_application', JSON.stringify(applicationData));

try {

const response = await fetch('https://app\.recruitsome\.com/api/v1/applications', {

method: 'POST',

headers: {

'Authorization': 'Bearer YOUR_API_KEY',

'Content-Type': 'application/json'

},

body: JSON.stringify(applicationData)

});

if (response.ok) {

localStorage.removeItem('pending_application');

const result = await response.json();

console.log('Application submitted:', result);

} else {

// Keep in local storage for retry

throw new Error(Failed: ${response.status});

}

} catch (error) {

console.error('Submission failed, will retry:', error);

}


python Python

import requests

import json

application_data = {

"vacancy": "senior-software-engineer-amsterdam",

"candidate": {

"given_name": "John",

"family_name": "Doe",

"email": "[email protected]"

}

}

Store locally first (example using file)

with open('pending_application.json', 'w') as f:

json.dump(application_data, f)

try:

response = requests.post(

'https://app\.recruitsome\.com/api/v1/applications',

headers={

'Authorization': 'Bearer YOUR_API_KEY',

'Content-Type': 'application/json'

},

json=application_data

)

if response.status_code == 201:

# Success - remove local storage

import os

os.remove('pending_application.json')

print('Application submitted:', response.json())

else:

raise Exception(f'Failed: {response.status_code}')

except Exception as e:

print(f'Submission failed, will retry: {e}')



Response Example

json

{

"message": "Application submitted successfully",

"data": {

"id": 456,

"vacancy": {

"id": 123,

"slug": "senior-software-engineer-amsterdam",

"title": "Senior Software Engineer"

},

"candidate": {

"id": 789,

"given_name": "John",

"family_name": "Doe",

"email": "[email protected]"

},

"status": "screening",

"source": "api",

"documents": [],

"submitted_at": "2024-01-20T14:30:00Z",

"created_at": "2024-01-20T14:30:00Z"

}

}




Submit an application with all highly recommended fields for the best chance of success:

bash cURL

curl -X POST https://app\.recruitsome\.com/api/v1/applications \

-H "Authorization: Bearer YOUR_API_KEY" \

-H "Content-Type: application/json" \

-d '{

"vacancy": "senior-software-engineer-amsterdam",

"candidate": {

"given_name": "John",

"family_name": "Doe",

"email": "[email protected]",

"mobile_phone": "+31612345678"

},

"documents": {

"resume": {

"filename": "john_doe_resume.pdf",

"content": "JVBERi0xLjQKJeLj...",

"mime_type": "application/pdf"

}

},

"privacy_policy_accepted": true

}'


javascript JavaScript

// Read file and convert to base64

async function fileToBase64(file) {

return new Promise((resolve, reject) => {

const reader = new FileReader();

reader.readAsDataURL(file);

reader.onload = () => {

const base64 = reader.result.split(',')[1];

resolve(base64);

};

reader.onerror = reject;

});

}

// Prepare application with resume

const resumeFile = document.getElementById('resume-input').files[0];

const resumeBase64 = await fileToBase64(resumeFile);

const applicationData = {

vacancy: "senior-software-engineer-amsterdam",

candidate: {

given_name: "John",

family_name: "Doe",

email: "[email protected]",

mobile_phone: "+31612345678"

},

documents: {

resume: {

filename: resumeFile.name,

content: resumeBase64,

mime_type: resumeFile.type

}

},

privacy_policy_accepted: true

};

// Store and submit with retry

async function submitWithRetry(data, attempt = 1) {

try {

const response = await fetch('https://app\.recruitsome\.com/api/v1/applications', {

method: 'POST',

headers: {

'Authorization': 'Bearer YOUR_API_KEY',

'Content-Type': 'application/json'

},

body: JSON.stringify(data)

});

if (response.ok) {

localStorage.removeItem('pending_application');

return await response.json();

} else if (response.status === 429 && attempt < 3) {

// Rate limited - retry with backoff

await new Promise(resolve => setTimeout(resolve, attempt * 2000));

return submitWithRetry(data, attempt + 1);

} else {

throw new Error(Failed: ${response.status});

}

} catch (error) {

localStorage.setItem('pending_application', JSON.stringify(data));

throw error;

}

}


python Python

import requests

import base64

import time

def submit_with_retry(data, attempt=1):

try:

response = requests.post(

'https://app\.recruitsome\.com/api/v1/applications',

headers={

'Authorization': 'Bearer YOUR_API_KEY',

'Content-Type': 'application/json'

},

json=data

)

if response.status_code == 201:

return response.json()

elif response.status_code == 429 and attempt < 3:

# Rate limited - retry with backoff

time.sleep(attempt * 2)

return submit_with_retry(data, attempt + 1)

else:

response.raise_for_status()

except Exception as e:

# Store for later retry

with open('pending_application.json', 'w') as f:

json.dump(data, f)

raise

Read resume file

with open('resume.pdf', 'rb') as f:

resume_content = base64.b64encode(f.read()).decode('utf-8')

application_data = {

"vacancy": "senior-software-engineer-amsterdam",

"candidate": {

"given_name": "John",

"family_name": "Doe",

"email": "[email protected]",

"mobile_phone": "+31612345678"

},

"documents": {

"resume": {

"filename": "john_doe_resume.pdf",

"content": resume_content,

"mime_type": "application/pdf"

}

},

"privacy_policy_accepted": True

}

result = submit_with_retry(application_data)



Complete Example

Submit a comprehensive application with all available fields:

bash cURL

curl -X POST https://app\.recruitsome\.com/api/v1/applications \

-H "Authorization: Bearer YOUR_API_KEY" \

-H "Content-Type: application/json" \

-d '{

"vacancy": 123,

"candidate": {

"given_name": "John",

"family_name": "Doe",

"email": "[email protected]",

"mobile_phone": "+31612345678",

"fixed_phone": "+31201234567"

},

"documents": {

"resume": {

"filename": "john_doe_resume.pdf",

"content": "JVBERi0xLjQKJeLj...",

"mime_type": "application/pdf"

},

"cover_letter": {

"filename": "cover_letter.pdf",

"content": "JVBERi0xLjQKJeLj...",

"mime_type": "application/pdf"

},

"additional": [

{

"filename": "portfolio.pdf",

"content": "JVBERi0xLjQKJeLj...",

"mime_type": "application/pdf",

"description": "My design portfolio"

}

]

},

"privacy_policy_accepted": true,

"tracking": {

"ip_address": "192.168.1.100",

"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",

"referrer": "https://jobboard.com/jobs/123"

}

}'



Request Fields

Required Fields

FieldTypeDescription
vacancystring\integerRequired. Vacancy identifier (slug, publication_slug, or ID)
candidate.given_namestringRequired. First/given name (max 255 chars)
candidate.family_namestringRequired. Last/family name (max 255 chars)
candidate.emailstringRequired. Valid email address (max 255 chars)

Highly Recommended Fields

<div class="not-prose rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"><p class="font-medium text-amber-800 dark:text-amber-300">Warning</p><p class="mt-1 text-sm text-amber-700 dark:text-amber-400">Applications without these fields have significantly lower success rates and may be automatically rejected by AI screening.</p></div>

FieldTypeDescription
documents.resumeobjectHighly recommended. CV/Resume document
candidate.mobile_phonestringHighly recommended. Mobile number in E.164 format (e.g., +31612345678)
privacy_policy_acceptedbooleanHighly recommended. Explicit consent for data processing

Optional Fields

FieldTypeDescription
candidate.fixed_phonestringLandline number in E.164 format
candidate.linkedin_urlstringLinkedIn profile URL (max 255 chars)
documents.cover_letterobjectMotivation letter
documents.additionalarrayUp to 5 additional documents
tracking.ip_addressstringCandidate's IP address
tracking.user_agentstringBrowser user agent (max 1000 chars)
tracking.referrerstringReferral URL (max 1000 chars)

Document Upload

Documents must be base64 encoded and include metadata:

Document Structure

json

{

"filename": "document.pdf",

"content": "base64_encoded_content_here",

"mime_type": "application/pdf"

}


Supported Document Types

CategoryAllowed MIME TypesMax Size
Resumeapplication/pdf<br/>application/msword<br/>application/vnd.openxmlformats-officedocument.wordprocessingml.document5MB
Cover LetterSame as Resume5MB
AdditionalSame as Resume + image/jpeg, image/png20MB

<div class="not-prose rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-800"><p class="mt-1 text-sm text-gray-700 dark:text-gray-300">- Maximum 5 additional documents

  • Total request size limit: 30MB
  • Base64 encoding increases file size by ~33%</p></div>

Document Upload Example

javascript

// Helper function to convert file to base64

async function prepareDocument(file) {

return new Promise((resolve, reject) => {

const reader = new FileReader();

reader.readAsDataURL(file);

reader.onload = () => {

const base64 = reader.result.split(',')[1];

resolve({

filename: file.name,

content: base64,

mime_type: file.type

});

};

reader.onerror = reject;

});

}

// Process multiple files

const resumeFile = document.getElementById('resume').files[0];

const coverLetterFile = document.getElementById('cover-letter').files[0];

const additionalFiles = document.getElementById('additional').files;

const documents = {

resume: await prepareDocument(resumeFile),

cover_letter: coverLetterFile ? await prepareDocument(coverLetterFile) : undefined,

additional: []

};

// Add additional documents

for (let i = 0; i < Math.min(additionalFiles.length, 5); i++) {

documents.additional.push(await prepareDocument(additionalFiles[i]));

}


Phone Number Format

Phone numbers must be in E.164 international format:

Valid Examples

  • +31612345678 (Netherlands mobile)
  • +12125551234 (US number)
  • +447911123456 (UK mobile)
  • +33612345678 (France mobile)

Invalid Examples

  • 0612345678 (missing country code)
  • +31 6 1234 5678 (contains spaces)
  • +31-6-12345678 (contains dashes)
  • (06) 12345678 (wrong format)

Phone Validation Helper

javascript

function validateE164(phone) {

const e164Regex = /^\+[1-9]\d{6,14}$/;

return e164Regex.test(phone);

}

function formatPhoneForE164(phone, countryCode = '31') {

// Remove all non-digits

let cleaned = phone.replace(/\D/g, '');

// Remove leading zeros

cleaned = cleaned.replace(/^0+/, '');

// Add country code if missing

if (!cleaned.startsWith(countryCode)) {

cleaned = countryCode + cleaned;

}

// Add + prefix

return '+' + cleaned;

}

// Examples

formatPhoneForE164('06-12345678', '31'); // +31612345678

formatPhoneForE164('(202) 555-1234', '1'); // +12025551234


Vacancy Identification

The vacancy field accepts three types of identifiers:

  1. Numeric ID: 123
  2. Vacancy slug: "senior-software-engineer"
  3. Publication slug: "senior-software-engineer-amsterdam"

<div class="not-prose rounded-lg border border-emerald-200 bg-emerald-50 p-4 dark:border-emerald-800 dark:bg-emerald-900/20"><p class="font-medium text-emerald-800 dark:text-emerald-300">Tip</p><p class="mt-1 text-sm text-emerald-700 dark:text-emerald-400">Use the publication slug from the vacancy list API for the most reliable matching.</p></div>

Error Handling

Validation Errors (422)

json

{

"message": "The given data was invalid.",

"errors": {

"candidate.email": [

"The candidate's email address is required."

],

"candidate.mobile_phone": [

"The candidate.mobile phone field must be a valid phone number."

],

"documents.resume": [

"The file size exceeds the maximum allowed limit."

]

}

}


Business Logic Errors

Vacancy Not Found (404)

json

{

"message": "Vacancy not found",

"error": "VACANCY_NOT_FOUND"

}


Vacancy Closed (400)

json

{

"message": "This vacancy is no longer accepting applications",

"error": "VACANCY_CLOSED"

}


Rate Limiting (429)

json

{

"message": "Too many requests. Please try again later.",

"retry_after": 60

}


<div class="not-prose rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"><p class="font-medium text-amber-800 dark:text-amber-300">Warning</p><p class="mt-1 text-sm text-amber-700 dark:text-amber-400">Rate limit: 10 requests per minute per API key</p></div>

Implementation Best Practices

1. Local Storage Pattern

javascript

class ApplicationQueue {

constructor() {

this.storageKey = 'pending_applications';

}

add(applicationData) {

const queue = this.getAll();

queue.push({

id: Date.now(),

data: applicationData,

attempts: 0,

lastAttempt: null

});

localStorage.setItem(this.storageKey, JSON.stringify(queue));

}

getAll() {

const stored = localStorage.getItem(this.storageKey);

return stored ? JSON.parse(stored) : [];

}

remove(id) {

const queue = this.getAll().filter(item => item.id !== id);

localStorage.setItem(this.storageKey, JSON.stringify(queue));

}

async processQueue() {

const queue = this.getAll();

for (const item of queue) {

if (item.attempts >= 3) continue; // Max retries reached

try {

await this.submit(item.data);

this.remove(item.id);

} catch (error) {

item.attempts++;

item.lastAttempt = Date.now();

this.update(item);

}

}

}

}


2. Validation Before Submission

javascript

function validateApplication(data) {

const errors = {};

// Required fields

if (!data.candidate?.given_name?.trim()) {

errors['candidate.given_name'] = 'Given name is required';

}

if (!data.candidate?.family_name?.trim()) {

errors['candidate.family_name'] = 'Family name is required';

}

if (!data.candidate?.email || !isValidEmail(data.candidate.email)) {

errors['candidate.email'] = 'Valid email is required';

}

// Highly recommended fields

const warnings = {};

if (!data.documents?.resume) {

warnings.resume = 'Adding a resume significantly improves your chances';

}

if (!data.candidate?.mobile_phone) {

warnings.mobile_phone = 'Mobile phone helps recruiters contact you quickly';

}

if (!data.privacy_policy_accepted) {

warnings.privacy = 'You must accept the privacy policy';

}

return { errors, warnings, isValid: Object.keys(errors).length === 0 };

}


3. Progress Feedback

javascript

class ApplicationSubmitter {

constructor(onProgress) {

this.onProgress = onProgress;

}

async submit(data) {

this.onProgress({ status: 'validating', progress: 10 });

const validation = validateApplication(data);

if (!validation.isValid) {

throw new ValidationError(validation.errors);

}

this.onProgress({ status: 'preparing', progress: 30 });

// Store locally

this.onProgress({ status: 'saving_locally', progress: 40 });

localStorage.setItem('current_application', JSON.stringify(data));

// Submit

this.onProgress({ status: 'submitting', progress: 60 });

try {

const response = await fetch('/api/v1/applications', {

method: 'POST',

headers: {

'Authorization': Bearer ${API_KEY},

'Content-Type': 'application/json'

},

body: JSON.stringify(data)

});

this.onProgress({ status: 'processing_response', progress: 80 });

if (response.ok) {

localStorage.removeItem('current_application');

this.onProgress({ status: 'complete', progress: 100 });

return await response.json();

} else {

throw new SubmissionError(response.status, await response.text());

}

} catch (error) {

this.onProgress({ status: 'error', progress: 0, error });

throw error;

}

}

}


4. Duplicate Prevention

javascript

function generateApplicationHash(data) {

const key = ${data.vacancy}_${data.candidate.email}_${Date.now()};

return btoa(key);

}

function isDuplicateSubmission(data, timeWindowMs = 60000) {

const hash = generateApplicationHash(data);

const submissions = JSON.parse(localStorage.getItem('recent_submissions') || '[]');

// Clean old submissions

const cutoff = Date.now() - timeWindowMs;

const recent = submissions.filter(s => s.timestamp > cutoff);

// Check for duplicate

if (recent.some(s => s.hash === hash)) {

return true;

}

// Add new submission

recent.push({ hash, timestamp: Date.now() });

localStorage.setItem('recent_submissions', JSON.stringify(recent));

return false;

}


Testing Your Integration

Test Scenarios

  1. Success Case: Valid application with all required fields
  2. Validation Errors: Missing required fields, invalid formats
  3. Network Failures: Timeout, connection errors
  4. Rate Limiting: Submit 11 requests within a minute
  5. Large Files: Test with 5MB resume
  6. Multiple Documents: Test with resume + cover letter + 5 additional

Test Data

javascript

const testApplication = {

vacancy: "test-vacancy-slug",

candidate: {

given_name: "Test",

family_name: "User",

email: "[email protected]",

mobile_phone: "+31612345678"

},

documents: {

resume: {

filename: "test_resume.pdf",

content: "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PgplbmRvYmoKMiAwIG9iago8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PgplbmRvYmoKMyAwIG9iago8PC9UeXBlL1BhZ2UvUGFyZW50IDIgMCBSL1Jlc291cmNlczw8L0ZvbnQ8PC9GMSA0IDAgUj4+Pj4vTWVkaWFCb3hbMCAwIDYxMiA3OTJdL0NvbnRlbnRzIDUgMCBSPj4KZW5kb2JqCjQgMCBvYmoKPDwvVHlwZS9Gb250L1N1YnR5cGUvVHlwZTEvQmFzZUZvbnQvSGVsdmV0aWNhPj4KZW5kb2JqCjUgMCBvYmoKPDwvTGVuZ3RoIDQ0Pj4Kc3RyZWFtCkJUIC9GMSAxMiBUZiAxMDAgNzAwIFRkIChUZXN0IFJlc3VtZSkgVGogRVQKZW5kc3RyZWFtCmVuZG9iagp4cmVmCjAgNgowMDAwMDAwMDAwIDY1NTM1IGYKMDAwMDAwMDAxNSAwMDAwMCBuCjAwMDAwMDAwNzQgMDAwMDAgbgowMDAwMDAwMTMxIDAwMDAwIG4KMDAwMDAwMDIyOSAwMDAwMCBuCjAwMDAwMDAzMDYgMDAwMDAgbgp0cmFpbGVyCjw8L1NpemUgNi9Sb290IDEgMCBSPj4Kc3RhcnR4cmVmCjQwMgolJUVPRg==",

mime_type: "application/pdf"

}

},

privacy_policy_accepted: true

};

``

Remember to handle personal data according to GDPR requirements. Only collect necessary information and ensure proper consent mechanisms are in place.