Updated June 15, 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 a candidate's application to a published vacancy — typically from the application form on your career website. One call delivers the candidate's details, their resume and other documents, and tracking information; Recruitsome creates the application, attaches the documents, and starts resume parsing automatically.
Authentication
Requires an API key with the applications:write scope (included in the Career Website preset). Calls with a key that lacks the scope return a 403 with error code INSUFFICIENT_SCOPE.
Tip
An application a candidate typed in is irreplaceable — don't let a network hiccup or a rate limit lose it. Store the submission locally (queue, database, or browser storage) before calling the API, and retry with backoff on any failure. Only discard your local copy after you receive the 201 response.
Body
| Field | Type | Required | Description |
|---|---|---|---|
vacancy | string or integer | yes | Vacancy identifier: a publication slug, a vacancy slug, or a numeric vacancy ID (see below) |
candidate | object | yes | The applicant's details |
candidate.given_name | string | yes | First/given name (max 255 chars) |
candidate.family_name | string | yes | Last/family name (max 255 chars) |
candidate.email | string | yes | Valid email address (max 255 chars) |
candidate.mobile_phone | string | no | Mobile number in international format (see phone notes below) |
candidate.fixed_phone | string | no | Landline number in international format |
candidate.linkedin_url | string | no | LinkedIn profile URL — must be a valid URL (max 255 chars) |
documents | object | no | Documents to attach (see below) |
privacy_policy_accepted | boolean | no | Whether the candidate accepted your privacy policy; recorded as consent with a timestamp |
tracking | object | no | Submission context |
tracking.ip_address | string | no | The candidate's IP address — must be a valid IP |
tracking.user_agent | string | no | Browser user agent (max 1000 chars) |
tracking.referrer | string | no | Referral URL — must be a valid URL (max 1000 chars) |
Vacancy identification
The vacancy field accepts three identifiers, resolved in this order:
- Numeric vacancy ID:
123 - Publication slug:
"maintenance-engineer-amsterdam"— as returned byGET /vacancies - Vacancy slug:
"maintenance-engineer"
Tip
Use the publication slug from the vacancy list API. It's the only identifier that lets Recruitsome attribute the application to the specific publication channel it came from.
Documents
Each document is an object with filename (max 255 chars), content (base64 encoded file data), and mime_type:
| Field | Allowed MIME types | Max size (decoded) |
|---|---|---|
documents.resume | application/pdf, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/zip, application/x-ole-storage, application/octet-stream | 5MB |
documents.cover_letter | Same as resume | 5MB |
documents.additional (array, max 5) | Any MIME type string | 20MB each |
Each entry in documents.additional may also carry a description (max 500 chars).
The application/zip, application/x-ole-storage, and application/octet-stream types are accepted because browsers and older Word files often misreport .doc/.docx MIME types — pass the type your file API gives you. Base64 encoding inflates file size by ~33%, and the total request body is capped at 30MB.
Phone numbers
mobile_phone and fixed_phone are validated as international phone numbers with lenient parsing: spaces and dashes are fine (+31 6 1234 5678 and +31-6-12345678 both pass), but the number must carry its country code — the leading + is what matters. A national-format number like 0612345678 fails with:
The mobile phone number must be in valid international format (e.g., +1234567890).Example
curl -X POST https://app.recruitsome.com/api/v1/applications \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"vacancy": "maintenance-engineer-amsterdam",
"candidate": {
"given_name": "Emma",
"family_name": "de Vries",
"email": "[email protected]",
"mobile_phone": "+31612345678"
},
"documents": {
"resume": {
"filename": "emma_de_vries_cv.pdf",
"content": "JVBERi0xLjQKJeLj...",
"mime_type": "application/pdf"
}
},
"privacy_policy_accepted": true,
"tracking": {
"ip_address": "84.241.196.34",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"referrer": "https://www.yourcompany.com/vacatures/maintenance-engineer"
}
}'const resumeFile = document.getElementById('resume-input').files[0];
const toBase64 = (file) =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result.split(',')[1]);
reader.onerror = reject;
});
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({
vacancy: 'maintenance-engineer-amsterdam',
candidate: {
given_name: 'Emma',
family_name: 'de Vries',
email: '[email protected]',
mobile_phone: '+31612345678',
},
documents: {
resume: {
filename: resumeFile.name,
content: await toBase64(resumeFile),
mime_type: resumeFile.type,
},
},
privacy_policy_accepted: true,
}),
});
if (response.status === 201) {
const result = await response.json();
console.log('Application submitted:', result.data.id);
}Response 201
{
"message": "Application submitted successfully",
"data": {
"id": 456,
"vacancy": {
"id": 123,
"slug": "maintenance-engineer",
"title": "Maintenance Engineer"
},
"applicant": {
"id": null,
"given_name": "Emma",
"family_name": "de Vries",
"email": "[email protected]"
},
"status": "screening",
"source": "api",
"documents": [
{
"id": 789,
"category": "resume",
"filename": "emma_de_vries_cv.pdf",
"size": 245120,
"mime_type": "application/pdf"
}
],
"submitted_at": "2026-06-11T14:30:00+00:00",
"created_at": "2026-06-11T14:30:00+00:00"
}
}The applicant.id is the linked candidate's ID when the applicant is already known as a candidate in Recruitsome, and null for a first-time applicant. Each entry in documents describes a stored document: id, category (resume, cover_letter, or other for additional documents), filename, size in bytes, and mime_type.
Errors
Validation errors 422
{
"message": "The given data was invalid.",
"errors": {
"candidate.email": [
"The candidate's email address is required."
],
"candidate.mobile_phone": [
"The mobile phone number must be in valid international format (e.g., +1234567890)."
],
"documents.resume": [
"The file size exceeds the maximum allowed limit."
]
}
}Vacancy not found 404
{
"message": "Vacancy not found",
"error": "VACANCY_NOT_FOUND"
}Vacancy closed 400
The vacancy exists but is no longer accepting applications:
{
"message": "This vacancy is no longer accepting applications",
"error": "VACANCY_CLOSED"
}Other business-rule rejections return a 400 with "error": "VALIDATION_ERROR" and the reason in message.
Authentication and authorization
| Status | Body | When |
|---|---|---|
401 | {"message": "Unauthenticated."} | Missing or invalid API key |
403 | {"message": "This API key does not have the required permission: applications:write", "error": "INSUFFICIENT_SCOPE", "required_scope": "applications:write"} | API key lacks the applications:write scope |
Rate limiting 429
The endpoint accepts 10 requests per minute per API key. Exceeding it returns the standard Laravel throttle response with a Retry-After header (seconds until the window resets):
{
"message": "Too Many Attempts."
}Server error 500
{
"message": "An unexpected error occurred while processing your application",
"error": "SUBMISSION_FAILED"
}A 500 can occur before or after the application was stored — the API does not deduplicate submissions, so don't retry blindly. Retry once, and if your retry returns a duplicate-looking success, reach out to [email protected] to deduplicate.
You're handling personal data: only collect what the vacancy genuinely needs, and pass privacy_policy_accepted so the candidate's consent is recorded with a timestamp in Recruitsome.