
I recently hit a hard wall while migrating a recruiting firm’s database from Recruitee to Manatal. The client had a massive, highly curated database of candidates, meticulously organized by custom tags. If you have ever worked in talent acquisition, you know that losing candidate tags is not an inconvenience—it is a catastrophic loss of intelligence.
Recruitee’s API is beautifully documented, making the extraction of these tags trivial. But when I went to push that data into the new Manatal instance, the pipeline broke.
The Bottleneck: The Missing Public Endpoint
Any respectable SaaS application in 2026 has an API, and Manatal does offer an "Open API" feature. However, public APIs are notoriously feature-lagging compared to the actual frontend application.
When you evaluate off-the-shelf integration tools or read through the standard endpoints for Manatal, you can programmatically create candidates, append notes, and upload attachments. But there is absolutely no documented public endpoint to apply a tag to a candidate profile.
If you rely on a productized, UI-driven migration tool, this is where your project dies. The vendor will simply tell you, "The target API does not support tags. You will have to input them manually."
For a database of thousands of candidates, manual data entry destroys the ROI of the migration. At ClonePartner, we do not accept "impossible" as an answer just because the documentation has a gap. If the web interface can perform the action, a script can perform the action.
The Trench Fix: Reverse-Engineering the Frontend API
Manatal, like most modern SaaS platforms, is a Single Page Application (SPA). When a user clicks "Add Tag" in their browser, the frontend executes an HTTP request to an internal, undocumented API.
Here is the exact engineering framework we used to bypass the public API limitations and preserve the client's taxonomy.
Step 1: Network Inspection
I asked the customer to provision a temporary admin login for us. I logged into the Manatal web app, opened Chrome DevTools, navigated to the Network tab, and manually added a tag to a test candidate.
By inspecting the XHR requests, I isolated the exact internal endpoint the frontend used. It wasn't routing through their public Open API infrastructure; it was an internal endpoint specifically handling the UI state.
Step 2: Payload and Header Mimicry
To interact with an internal API, your code must mimic a web browser exactly. I extracted the request headers, isolating the Authorization Bearer token (which the SPA stored in local storage) and the CSRF tokens required to prevent Cross-Site Request Forgery rejections.
I mapped the exact JSON payload structure required to associate a string tag with a specific Candidate UUID.
Step 3: The Custom Automation Script
With the endpoint, headers, and payload structure mapped, I wrote a custom Node.js script. This script ingested the transformed CSV of Recruitee tags, mapped them to the corresponding newly generated Manatal Candidate IDs, and fired authenticated POST requests directly to the internal API.
1// Conceptual Node.js snippet replicating the internal API call
2async function applyTagToCandidate(candidateId, tagName, sessionToken, csrfToken) {
3 const internalEndpoint = `https://app.manatal.com/internal-api/candidates/${candidateId}/tags`;
4
5 const response = await fetch(internalEndpoint, {
6 method: 'POST',
7 headers: {
8 'Authorization': `Bearer ${sessionToken}`,
9 'Content-Type': 'application/json',
10 'X-CSRF-Token': csrfToken,
11 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...'
12 },
13 body: JSON.stringify({ tag_name: tagName })
14 });
15
16 if (!response.ok) {
17 throw new Error(`Failed to apply tag. HTTP Status: ${response.status}`);
18 }
19}Failure Modes, Idempotency, and Session Expiry
When you write scripts against undocumented APIs, you operate entirely without a safety net. You must engineer your own guardrails to prevent data corruption.
1. Session Expiry (HTTP 401 Unauthorized)
Unlike permanent API keys, frontend session tokens expire quickly—often within hours. We couldn't run a massive candidate migration on a single token. To solve this, I built a headless browser routine using Puppeteer. When the primary script caught a 401 Unauthorized error, the headless browser automatically logged back into the Manatal UI, scraped the fresh session token from local storage, and fed it back into the Node.js worker to resume the queue.
2. Silent Rate Limiting (HTTP 429 or HTTP 403)
Internal APIs do not publish their rate limits. If you hammer the server, Web Application Firewalls (WAFs) like Cloudflare will permanently block your IP address. We throttled our script to a conservative two concurrent requests, injecting a randomized 500ms to 1500ms delay between calls to simulate organic human traffic.
3. Idempotency and Duplication (HTTP 400)
Internal endpoints rarely return helpful "Tag already exists" errors; they often just crash or, worse, create duplicate UI elements. We built a validation step into the script to perform a lightweight GET request, checking the candidate's existing tags before ever firing the POST request.
Decision Matrix: Public API vs. Reverse-Engineering
Reverse-engineering an internal API should never be your first choice. It introduces technical debt and brittleness. Use this strict matrix to determine when to take the leap:
Metric / Scenario | Use Official Public API | Reverse-Engineer Internal API |
Endpoint Availability | Documented and officially supported | Missing, feature-gated, or completely undocumented |
Authentication | Permanent API Key or OAuth | Short-lived Session Tokens or Cookies |
Maintenance Burden | Low (Vendor guarantees contract) | High (Breaks instantly if vendor updates UI) |
Rate Limits | Published SLAs with Retry-After headers | Undocumented; requires WAF evasion and throttling |
Business Impact | Standard data (Names, Emails) | Critical taxonomy (Tags, Custom Relations) with no manual fallback |
When evaluating migration vendors, pay attention to how they handle the word "impossible." Productized migration plugins sell you convenience, but they fail the moment they hit an edge case the original developers didn't account for.
By leveraging a custom service-based approach, we didn't just map the standard fields; we preserved the exact taxonomy the client relied on. When the documentation says no, a skilled engineer opens DevTools.