CV Automation
From LinkedIn to Teamtailor in Seconds: Automating Candidate Import with n8n
Sourcing candidates on LinkedIn Recruiter is great. Until you have to manually copy their details into your ATS. Download the CV, open Teamtailor, create a candidate, fill in the fields, upload the file. Repeat 20 times a week. So I built an automation that does all of it in the background.
Drop a CV into a Google Drive folder and it automatically creates the candidate in Teamtailor, uploads the resume to the right field, and links them to the correct job opening. No ATS required.
1. The Problem
Our recruitment team was sourcing candidates on LinkedIn Recruiter and manually importing them into Teamtailor. The process was:
- Find a candidate on LinkedIn
- Download their CV (LinkedIn allows up to 25 at a time, 200/month per seat)
- Open Teamtailor
- Create a new candidate manually
- Upload the CV
- Fill in name, email, job position
- Repeat
The LinkedIn Recruiter System Connect (RSC) integration with Teamtailor only works when candidates apply through a job posting. It does nothing for sourced candidates. The CV also never lands in the right field for automatic parsing.
2. The Tech Stack
| Layer | Technology |
|---|---|
| Workflow Automation | n8n (self-hosted) |
| File Storage | Google Drive |
| CV Upload | Teamtailor Transient Upload API |
| ATS | Teamtailor |
| Trigger | Google Drive polling (every minute) |
3. The Automation Pipeline
The workflow runs entirely in n8n and has 6 steps:
CV dropped in Google Drive folder
│
▼
┌─────────────────────────┐
│ Watch CV Folder │
│ Google Drive Trigger │
│ (polls every minute) │
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ Download CV │
│ Fetches binary PDF │
│ from Drive │
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ Upload to Teamtailor │
│ POST /v1/files │
│ Returns transient URI │
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ Create Candidate │
│ POST /v1/candidates │
│ resume = transient URI │
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ Find Job by Title │
│ GET /v1/jobs │
│ filter by filename │
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ Create Job Application │
│ POST /v1/job-apps │
│ links candidate + job │
└─────────────────────────┘
4. Key Technical Decisions
Teamtailor Transient Upload
The Teamtailor public API doesn't support binary-data in the candidate creation endpoint. That's only available in the Direct Apply API for job board partners. The correct approach for importing external CVs is the transient upload endpoint:
POST https://api.teamtailor.com/v1/files
Content-Type: multipart/form-data
file: [binary PDF]
This returns a one-time transient: URI that you then pass as the resume value when creating the candidate. Teamtailor downloads it, parses the CV, and auto-fills name, phone, LinkedIn URL, and address from the document.
Job Matching via Filename Convention
Instead of hardcoding job IDs or building a mapping table, the workflow uses a simple filename convention:
EMEA Marketing Intern - Berta Carol Jutgla.pdf
SDR SP Intern - John Smith.pdf
The text before the - separator is extracted as the job title, then used in a live API call:
GET /v1/jobs?filter[title]=EMEA Marketing Intern
This means no configuration is needed when new job openings are created in Teamtailor. The workflow finds them dynamically by name.
Why Not Google Drive Desktop Sync?
The Google Drive desktop client introduces a sync delay. A file can appear in the local folder before it's fully uploaded to Google's servers. When n8n polls and detects the file, it may try to download an incomplete upload. Using browser upload directly on drive.google.com ensures the file is fully available server-side before the trigger fires.
5. What Gets Auto-Filled
When the CV lands in Teamtailor's resume field (not as an attachment), the built-in resume parser automatically extracts:
- ✅ Full name
- ✅ Phone number
- ✅ LinkedIn URL
- ✅ Address / location
- ✅ CV summary (via Co-pilot)
The email address is not extracted automatically. Teamtailor's parser skips it for privacy reasons. A placeholder email is used at creation time and updated manually when the recruiter makes contact.
6. The Workflow in n8n
The complete workflow has 8 nodes:
| Node | Type | Action |
|---|---|---|
| Watch CV Folder | Google Drive Trigger | Detects new files in the folder |
| Download CV | Google Drive | Downloads binary PDF |
| Upload to Teamtailor | HTTP Request | POST to /v1/files, gets transient URI |
| Create Candidate | HTTP Request | POST to /v1/candidates with URI as resume |
| Extract Candidate ID & Job Title | Code | Parses candidate ID + extracts job title from filename |
| Find Job by Title | HTTP Request | GET /v1/jobs filtered by title |
| Prepare Job Application | Code | Extracts job ID, pairs with candidate ID |
| Create Job Application | HTTP Request | POST to /v1/job-applications |
All HTTP nodes use application/vnd.api+json as Content-Type (Teamtailor follows JSON:API spec).
7. Lessons Learned
The Teamtailor API is JSON:API. All requests need Content-Type: application/vnd.api+json, not application/json. This caused silent 500 errors for hours before being identified.
Binary data in n8n Code nodes. Accessing binary data from a previous node requires items[0].binary.data.data, which already contains a base64 string. The getBinaryDataBuffer helper doesn't work reliably in all contexts.
Google Drive Trigger processes multiple files. When several CVs are dropped at once, the trigger returns multiple items. All Code nodes must be set to "Run Once for Each Item" mode, otherwise only the first file is processed.
Transient URIs are single-use. They must be used within ~30 seconds of generation. The workflow is fast enough that this isn't an issue in practice.
Conclusion
What used to take 5 minutes per candidate now takes 5 seconds. The recruiter downloads CVs from LinkedIn, renames them with the job title prefix, drops them in the Drive folder, and Teamtailor does the rest.
The most interesting part of this build was discovering the Teamtailor transient upload API. A non-obvious endpoint that's not prominently documented, but the correct way to import CVs programmatically into the resume field rather than as generic file attachments.
Built by Nuno, Veesion Tech & IT