Merge & join CSV files
Combining two CSV files on a common key is exactly the kind of task that is trivial in a database and painful in a spreadsheet, especially once row counts climb past what VLOOKUP can handle gracefully. This endpoint takes two files and a join key and returns one merged dataset, using the same inner, left and outer join logic relational databases have relied on for decades.
Run — free
Runs in your browser. Free, unlimited — your data never leaves this page.
The everyday problem behind a merge
Two systems rarely export data in one tidy file. Orders live in one export, customer details in another; product catalogs sit apart from inventory counts; a marketing tool's contact list and a billing system's account list share only an email address as common ground. Reconciling them by hand means opening both files, sorting by the shared column, and manually stitching rows together, a process that is slow, error-prone, and has to be repeated every time either source refreshes.
Choosing the right kind of join
Call POST /data/merge-csv with two files and the column that links them, and specify whether you want an inner join, keeping only rows present in both files; a left join, keeping every row from the first file and filling gaps where no match exists; or an outer join, keeping every row from both files regardless of match. This distinction matters more than it looks: an inner join silently drops customers who have not ordered yet, while a left join preserves them with empty order fields, and picking the wrong one can quietly corrupt a downstream report.
Where join logic actually comes from
Inner, left and outer joins are not an invention of this endpoint; they come straight from relational algebra, the mathematical foundation SQL databases have used since the 1970s to combine tables. Bringing that same rigor to plain CSV files means you get predictable, well-understood behavior — the same behavior a JOIN clause would produce in a database — without needing to load the files into one first.
What happens on mismatched or duplicate keys
Real files rarely align perfectly: keys can be missing from one side, duplicated, or formatted inconsistently, like a numeric ID stored as text with leading zeros in one file and without them in the other. The merge follows exact key matching as configured, so cleaning or normalizing the join column beforehand — through a transform step, for instance — produces the most reliable results, particularly on files where one side has more duplicate keys than the other.
Fitting into a larger pipeline
Because the merge runs asynchronously and hands back a task_id right away, it composes naturally with other steps: transform a column first, merge two sources next, filter the combined result afterward. Automated reporting pipelines, nightly reconciliation jobs and one-off analyst requests all benefit from not having to load two files into a spreadsheet or write a throwaway script just to combine them.
What you can do with it
Orders and customer records
Merge an orders export with a customer list on customer_id to get one file showing what each customer bought, without touching a database.
Inventory and product catalog reconciliation
Join a warehouse stock-count file with a product catalog on SKU to see current stock alongside product names and categories.
Cross-tool marketing and billing data
Combine a marketing platform's contact export with a billing system's account list on email address to see engagement next to revenue.
Multi-source reporting
Left-join a master employee list against a training-completion export so managers can see who has not completed required training, not just who has.
FAQ
What join types does this CSV merge api support?
Inner, left and outer joins, matching the same logic a SQL JOIN clause uses, so you can choose whether unmatched rows are dropped or kept.
Can I merge more than two CSV files at once?
Each request merges two files on one key; chain multiple merge tasks together to combine three or more files, feeding the output of one into the next.
What if the join key has different formatting in each file?
The join matches keys exactly as provided, so normalizing formatting differences, like leading zeros or letter case, beforehand with a transform step gives the most reliable match rate.
Is there a free tier to test the merge endpoint?
The tool above runs free in your browser. The API is paid — each call draws from your prepaid ForHosting KIT balance: top up from $10.00 (it never expires), pay each request's published price, and a call with no balance returns HTTP 402. No subscription, no tokens, and a failed task is never charged.
What happens to duplicate keys during a join?
Each matching pair of rows is included in the output, so a key duplicated on one side produces multiple output rows, exactly as a database join would.
Can I merge large CSV files?
Yes, submit the merge as an async request and it returns a task_id immediately, so large files process in the background without blocking your pipeline.
How do I receive the merged file?
Through a signed webhook, recommended for automation, or a signed link valid for 24 hours if you want to download it manually.
How much does the CSV merge API cost?
$0.002 per request, charged only when the merge completes successfully.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, and soon from our app, email and Telegram.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/data/merge-csv \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"…"}'const res = await fetch("https://api.kit.forhosting.com/data/merge-csv", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"text": "…"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/data/merge-csv",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"text": "…"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/data/merge-csv", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"text":"…"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"text":"…"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/data/merge-csv", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"text": "…"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "data.merge_csv",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_mb | 25 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |