Web table to CSV
Some of the best data on the internet is still trapped in an HTML table with no download button: a pricing grid, a government dataset, a sports schedule, a stock table refreshed nightly. This endpoint scrapes those tables straight into CSV, columns and headers intact, ready to drop into a spreadsheet or a database.
Run it online
Run this on our servers with your account. Free tools run in your browser; this one bills your KIT balance per the price above.
The gap between 'visible' and 'usable' data
An HTML table renders beautifully in a browser and is almost useless everywhere else: copy-pasting from a page mangles the columns, breaks merged cells, and drags in ads or footnotes you never wanted. Analysts, researchers and finance teams routinely need that same data in a spreadsheet, and until now the only options were a manual copy job or a custom scraper written just for that one site, an approach that breaks the moment the site's markup changes even slightly.
How the extraction works
Submit one or more URLs and this endpoint locates the table (or tables) on each page, reads header rows to label columns correctly, and converts the result to CSV, preserving row order and handling the awkward cases that trip up naive scrapers, like colspan headers or tables built with nested divs instead of proper markup. The call returns a task_id immediately, and the finished CSV arrives by webhook, or through a signed link kept valid for 24 hours, without you needing to keep a connection open while the extraction runs.
A format with history behind it
The HTML table element predates CSS layout entirely; before flexbox and grid, tables were how the web laid out pages, and remnants of that era still show up as data tables with inconsistent structure. CSV, on the other hand, has been the lowest common denominator for tabular data since long before the web existed, which is exactly why it's still the target every spreadsheet, database and BI tool understands without a plugin.
Built for batches, not one-off copies
Because pricing is per URL on top of the base request, this is designed to run across dozens or hundreds of pages in one call, useful for tracking a competitor's pricing table over time, compiling a public dataset spread across many pages, or turning an entire archive of report pages into one clean data pipeline feeding a warehouse. The cost stays predictable at any scale, since it's a fixed base plus a fixed per-URL rate rather than a variable charge tied to table size.
What you can do with it
Competitor pricing pages
Pull pricing tables from multiple competitor sites on a schedule and compare them as structured rows instead of screenshots.
Public dataset compilation
Turn a government or research site's paginated tables into a single CSV ready for analysis, without hand-copying each page.
Schedule and roster tracking
Extract event schedules, standings or rosters published only as an HTML table on a league or venue website.
Financial reporting archives
Convert historical filings or rate tables published as web pages into CSV for a finance team's spreadsheet models.
FAQ
How do I extract a table from a website with this API?
POST the URL (or a list of URLs) to /web/tables; you get a task_id right away and the CSV output arrives by webhook or a signed link once processing finishes.
Is this table extraction API free?
There's no free tier — free tiers get abused and slow everyone down. Access runs on a prepaid ForHosting KIT balance: top up from $10.00 (it never expires) and each request is charged at its published price, so a call with no balance returns HTTP 402. No subscription, no tokens, no invented credits, and a failed task is never charged.
What output format do I get?
The result is CSV with header rows correctly labeled as columns, ready to open in a spreadsheet or load into a database.
Can it handle a page with multiple tables?
Yes, each table found on a page is captured and returned distinctly rather than merged together.
Does it work on tables built without proper HTML table markup?
It's built to read genuine table structures reliably; pages that fake a table purely with div layout may extract less cleanly than standard markup.
Can I extract tables from many URLs in bulk?
Yes, submit a list of URLs in one call and pay the per-URL rate on top of the base request price.
How do I receive the extracted CSV?
Choose a signed webhook for automatic delivery or fetch the CSV from a signed link, valid for 24 hours after the task completes.
Is my extracted data kept or used for training?
No. Data is deleted after the retention window and is never used to train any model.
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/web/tables \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://ejemplo.com"}'const res = await fetch("https://api.kit.forhosting.com/web/tables", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"url": "https://ejemplo.com"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/web/tables",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"url": "https://ejemplo.com"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/web/tables", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"url":"https://ejemplo.com"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"url":"https://ejemplo.com"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/web/tables", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"url": "https://ejemplo.com"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "web.tables",
"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
timeout_sec | 30 |
max_crawl_pages | 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. |
422 | task_failed | The task failed after 3 retries. You are never charged for it. |