Everything the KIT does, in one place
ForHosting KIT is a catalog of 7447 ready-made tasks — convert a document, read an invoice, transcribe audio, validate an IBAN, generate a QR — across 14 categories. Run them here on the web, or call them from your code with one authenticated POST. This page is the complete API reference: endpoints, the async model, the webhook contract, errors and pricing.
What the KIT is
A task catalog, not a model
Every capability is a task: you send input, you get a result. There are no tokens, no context windows and no prompt engineering. A task has a published price, a documented unit and a fixed shape.
Four ways to run one. Web — every capability has its own page and runs in the browser; the free ones never leave your device. API — one authenticated POST, documented below. Email and Telegram — send the task to a KIT address. The API is one channel, not the product.
Quickstart
From nothing to a result in three calls
Create an account, get your key, run a task. No sales call, no waiting list.
Get an API key
POST /signup with your email returns a key that starts with kit_live_. It is shown once. We store only its SHA-256 hash, so if you lose it we cannot recover it — we issue a new one.
Pick a capability
GET /catalog lists all 7447 with their live price and unit. Or browse the catalog at the bottom of this page.
Run it
POST to the capability's endpoint. You get a task_id back immediately and the result arrives at your webhook.
Authentication
Bearer token, shown once
Every API request carries Authorization: Bearer kit_live_…. Keys are 48 hex characters after the prefix.
We store only the SHA-256 hash of your key. That is deliberate: a database dump does not hand anyone your credentials — but it also means we genuinely cannot email you your key back. Lost it? We revoke and issue.
A bad key always returns 401 and never says why. Revoked, mistyped and never-existed are indistinguishable on purpose: telling you which one it was is telling an attacker too.
The async model
Every task is asynchronous. No exceptions.
You POST the task
Returns 202 with a task_id and status queued, plus what it will cost. The money is held, not charged.
It runs on the edge
Sub-second for compute tasks; seconds for AI and media.
The result finds you
It is POSTed to your webhook_url if you gave one. Otherwise GET /tasks/{id}/result. Results are kept by size — from 168 h for small ones (up to 1 MB) down to 6 h for the largest.
Failure costs nothing
A task is attempted up to 3 times in total, with backoff between attempts. If it still fails, the hold is released and you are not charged. Ever.
API reference
Every endpoint the KIT answers
Routes carry no version prefix. /v1/* still resolves for older clients, but it is not the canonical form and new code should not use it.
Base URL: https://api.kit.forhosting.com
| Method | Route | Auth | What it does |
|---|---|---|---|
ANY |
/ |
Public | Service index: version, capability count and the endpoint list the API announces about itself. No key, and it answers any method. |
POST |
/{alias} |
API key | Per-capability shortcut for POST /tasks with type fixed — e.g. POST /ocr/invoice. This is the form each capability page shows. |
GET |
/account |
API key | Account balance: available_usd is your real wallet balance, plus held_usd. When the wallet is managed by the client area, balance.source is "portal" and balance_endpoint points to the live figure. |
POST |
/agent/ask |
API key Soon | Not implemented — returns 501 with a key, 401 without one. The conversational assistant is being built elsewhere; use POST /catalog/search to find a capability. |
GET |
/catalog |
Public | All capabilities with live price and unit. ?lang=en|es, ?q= to filter, ?limit=, ?schema=1 for the input schema, and ?channel= to get the price already adjusted for that channel — ask for the channel you will bill on, or you will show one figure and charge another. |
POST |
/catalog/search |
Public | {query} → the capabilities that match, each with its price already written out. No key. It scores by whole-word coverage with a threshold, so a query it doesn't understand returns nothing instead of guessing — that is deliberate. |
POST |
/estimate |
API key | {type, input} → units, price and breakdown. Quotes without running. When the real quantity can't be known upfront (the page count of a PDF behind a URL), the reply says estimated: true. |
GET |
/mobile/bootstrap |
Public | What the mobile app needs to start: categories, labels and the same channel-adjusted prices. No key. Not part of the public contract either — same reason as above. |
GET |
/mobile/catalog |
Public | Catalogue projection for the mobile app, with prices already adjusted for the app channel. No key. It is not part of the public contract: its shape follows the app and can change without notice — build against GET /catalog. |
GET |
/plans |
Public | Top-up amounts: currency and topup (sku, default_amount, min_amount). Nothing else — there are no plans to subscribe to. |
POST |
/signup |
Public | {email} → 201 with your api_key, shown once. 409 if the email exists; 429 over 10/h per IP. |
GET |
/tasks |
API key | Your tasks. ?status=, ?limit= (25 default, 100 max). |
POST |
/tasks |
API key | {type, input, webhook_url?, max_cost_usd?} → 202. You are billed for the task's real unit — pages, minutes, images — measured while it runs, not for the upfront estimate. max_cost_usd is a hard ceiling: if the real cost exceeds it the task fails and nothing is charged. Send Idempotency-Key to make retries safe: a replay returns the original task with idempotent: true. |
DELETE |
/tasks/{id} |
API key | Cancel a queued task and release its hold. |
GET |
/tasks/{id} |
API key | Task status. 10 reads every 10 seconds per task; over that, 429 with Retry-After: 1. Prefer the webhook. |
GET |
/tasks/{id}/events |
API key Soon | Not implemented — returns 501. SSE is coming; use the webhook. |
GET |
/tasks/{id}/result |
API key | JSON result, or the file as an attachment. 409 not ready, 410 expired, 422 failed. Retention depends on result size: 168 h for small results, down to 6 h for very large ones. |
POST |
/tasks/{id}/retry |
API key | Re-queue a failed task. |
POST |
/uploads |
API key | Send a local file: raw binary body, Content-Type of the file. → 201 with ref: "kit://upl_…", which you then put wherever a URL would go: {"input": {"pdf": "kit://upl_…"}}. One upload can feed several tasks. Refs live 24 h. Max 100 MiB (104.9 MB) per upload; each capability applies its own limit on top. |
Webhooks
Signed delivery, and how to verify it
Set webhook_url when you create a task and we POST the result there when it is ready. This is the recommended path: it costs less than polling and arrives sooner.
Verify the signature before you trust the body. Every delivery carries KIT-Signature: v1=<hex> and KIT-Timestamp: <unix seconds>. The signature is HMAC-SHA256 over the string <timestamp>.<raw body> — the timestamp and the dot are part of the signed payload, not decoration. Sign the raw bytes you received, not a re-serialized object.
Delivery is attempted up to 5 times with exponential backoff. A 4xx from your endpoint stops retrying immediately — we read it as “your handler is wrong”, not “try later”. Only 5xx and network errors retry. After that the delivery is dead-lettered.
What we POST
{
"event": "task.completed",
"created_at": "2026-07-16T10:31:04.120Z",
"data": {
"task_id": "tsk_a1b2c3d4e5f6",
"type": "ocr.invoice",
"status": "done",
"units": 1,
"price_usd": 0.021,
"result_url": "https://api.kit.forhosting.com/tasks/tsk_a1b2c3d4e5f6/result"
}
}{
"event": "task.failed",
"created_at": "2026-07-16T10:31:04.120Z",
"data": {
"task_id": "tsk_a1b2c3d4e5f6",
"type": "ocr.invoice",
"status": "failed",
"error": {
"code": "engine_error",
"message": "Upstream timed out after 3 attempts."
},
"charged": false
}
}Verifying the signature
const crypto = require("crypto");
// req.body tiene que ser el cuerpo CRUDO, no un objeto re-serializado.
function verify(rawBody, headers, secret) {
const sig = (headers["kit-signature"] || "").replace(/^v1=/, "");
const ts = headers["kit-timestamp"];
const mine = crypto.createHmac("sha256", secret)
.update(ts + "." + rawBody) // el timestamp va firmado
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(mine));
}import hmac, hashlib
def verify(raw_body: bytes, headers, secret: str) -> bool:
sig = headers["KIT-Signature"].removeprefix("v1=")
ts = headers["KIT-Timestamp"]
mine = hmac.new(secret.encode(),
f"{ts}.".encode() + raw_body, # el timestamp va firmado
hashlib.sha256).hexdigest()
return hmac.compare_digest(sig, mine)Errors
Standard HTTP, machine-readable slug
Every error carries a stable error slug in the body. Match on the slug, not the message: messages are localized and may change.
| HTTP | Error | Meaning |
|---|---|---|
400 | flow_depth | A composed capability nested deeper than allowed. Flatten the steps. |
400 | invalid_amount | The amount is missing, not a number, or outside the allowed range. |
400 | invalid_email | The email address is missing or malformed. |
400 | invalid_input | The input is missing a required field or isn't the shape this task expects. |
400 | invalid_json | The request body isn't valid JSON. |
400 | missing_type | No 'type' field on the request. See GET /catalog. |
400 | unknown_op | Unknown operation for this capability. Check the operation name against the catalogue. |
400 | unsafe_url | The URL points somewhere we will not fetch from — an internal or non-public address. |
401 | auth_required | This step needs an identified account and the request has none. |
401 | no_key | No API key on the request, or the account has none active. |
401 | unauthorized | Missing or invalid API key. |
402 | account_suspended | Your account is suspended — usually the per-account spend cap. Contact us to lift it. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
402 | max_cost_exceeded | The task cost more than the max_cost_usd you set. Nothing was charged. |
403 | forbidden | The credential is valid but not allowed to do this. |
404 | input_not_found | That kit:// reference does not exist. Upload the file again with POST /uploads. |
404 | not_found | No task or resource at that path. |
404 | unknown_type | That task type doesn't exist. |
409 | alias_taken | That inbound email alias belongs to someone else. Pick another. |
409 | already_accepted | That acceptance was already used. Each one works exactly once. |
409 | email_taken | An account already exists with that email. |
409 | need_lease | Another worker is already running this task. Wait for it to finish. |
409 | not_cancellable | The task is no longer queued, so it cannot be cancelled. Only queued tasks can. |
409 | not_ready | The task isn't finished yet. Poll its status or wait for the webhook. |
409 | not_retryable | Only failed tasks can be retried. This one is in another state. |
410 | expired | The result's retention window has passed and it is no longer stored. |
410 | input_expired | That kit:// reference has expired. Uploads live 24 h; upload the file again. |
410 | quote_expired | The quote is older than its validity window. Ask for a new one. |
413 | input_too_large | The file exceeds the size limit. |
413 | resolution_too_high | The image or video exceeds the resolution this capability accepts. |
422 | conversion_failed | The file could not be converted. It is usually a corrupt or unexpected format. |
422 | engine_error | The engine failed after every attempt. The hold was released — you are not charged. |
422 | needs_rework | The result did not pass its own quality check, so it is not delivered. Not charged. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
500 | lease_error | The task could not be leased for execution. It goes back to the queue. |
500 | ledger_error | The balance couldn't be held or settled. Try again. |
500 | no_hold | The task has no reservation to settle. It should not happen; if it does, tell us. |
500 | no_result | The task finished but produced no result. |
501 | coming_soon | Capability being deployed (Phase 1b). Its page exists; it doesn't accept executions yet. |
501 | mail_not_configured | This capability sends email and the account has no sender configured yet. |
501 | not_implemented | This endpoint or capability isn't available yet. |
501 | tickets_not_configured | The ticket integration is not set up for this account. |
501 | unsupported | This operation isn't supported yet (for example, flows can't be quoted). |
502 | model_output_invalid | The model returned something that does not match the declared output. Not charged. |
502 | tickets_unreachable | The ticket system did not answer. Nothing was charged; try again. |
503 | all_busy | Every worker for this capability is busy. Retry shortly. |
Pricing
Published, per task, no credits
Pay only for what you use. Add balance to your account (from $10.00) — it never expires — and every task draws from it at its published per-task price. Real dollars, not points.
Each task costs a base rate plus a unit rate, both published on the capability's own page and in the catalog below — from $0.002 per call. POST /estimate quotes a task without running it, and max_cost_usd on a task refuses it if it would cost more than you said.
Failed tasks are never charged. Free capabilities run in your browser and cost nothing at all.
Limits
What the service enforces
Three limits, and they are keyed differently. Per account: 600 requests a minute overall, and 60 uploads a minute. Per task: 10 status reads every 10 seconds — over any of them you get 429 with Retry-After: 1. Use the webhook instead of polling: it costs less and arrives sooner. Results are kept by size: 168 h for results up to 1 MB, down to 6 h for very large ones. GET /tasks returns 25 by default, 100 maximum. Signup is limited to 10 accounts per hour per IP.
Capability catalog
All 7447, browsable by category
Every capability has its own page, where you can run it, see a real example and the published price — the same price this API bills. Open the full catalogue, or jump straight to a category.
No warranty
What we do not promise
The KIT is provided “as is”: no warranty, no uptime commitment, no SLA (dedicated infrastructure with an SLA is a paid add-on) and no guaranteed support. See the Terms for the full KIT clause.