Updated April 22, 2026
/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
- Validate input locally before submission
- Store application data in local storage or database
- Show status to user
- Submit to API with proper error handling
- On success (201): Clear local storage and show confirmation
- 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"
}
}
Recommended Example
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
Field Type Description vacancy string\ integer Required. Vacancy identifier (slug, publication_slug, or ID) candidate.given_name string Required. First/given name (max 255 chars) candidate.family_name string Required. Last/family name (max 255 chars) candidate.email string Required. 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>
Field Type Description documents.resume object Highly recommended. CV/Resume document candidate.mobile_phone string Highly recommended. Mobile number in E.164 format (e.g., +31612345678) privacy_policy_accepted boolean Highly recommended. Explicit consent for data processing
Optional Fields
Field Type Description candidate.fixed_phone string Landline number in E.164 format candidate.linkedin_url string LinkedIn profile URL (max 255 chars) documents.cover_letter object Motivation letter documents.additional array Up to 5 additional documents tracking.ip_address string Candidate's IP address tracking.user_agent string Browser user agent (max 1000 chars) tracking.referrer string Referral 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
Category Allowed MIME Types Max Size Resume application/pdf<br/>application/msword<br/>application/vnd.openxmlformats-officedocument.wordprocessingml.document 5MB Cover Letter Same as Resume 5MB Additional Same as Resume + image/jpeg, image/png 20MB
<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]));
}
javascript+31612345678Phone Number Format
Phone numbers must be in E.164 international format:
Valid Examples
(Netherlands mobile)+12125551234(US number)+447911123456(UK mobile)+33612345678(France mobile)0612345678Invalid Examples
(missing country code)+31 6 1234 5678(contains spaces)+31-6-12345678(contains dashes)(06) 12345678(wrong format)Phone Validation Helper
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
jsonvacancyVacancy Identification
The
field accepts three types of identifiers:123
- Numeric ID:
"senior-software-engineer"Vacancy slug: "senior-software-engineer-amsterdam"Publication slug: <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)
{
"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
- Success Case: Valid application with all required fields
- Validation Errors: Missing required fields, invalid formats
- Network Failures: Timeout, connection errors
- Rate Limiting: Submit 11 requests within a minute
- Large Files: Test with 5MB resume
- 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.