Shorten a link
A short link is a small thing until the domain in it isn't yours, and suddenly every click carries someone else's brand instead of your product's. This API turns any long URL into a short one served from your own domain, returned asynchronously so it fits into signup flows, content pipelines and marketing tools without slowing them down.
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.
Why the domain in the link matters
A shortened link built on a generic shared domain is instantly recognizable as third-party, and recipients treat it accordingly — some email filters flag them, some users hesitate to click. Running the redirect on a domain your audience already trusts changes that calculus. dev.short_url is built around that distinction: you point it at a subdomain you control, and every link it produces reflects your brand, not a stranger's.
What actually happens on each call
You submit the destination URL, and optionally a preferred slug, custom expiry, or other routing hints. The task is queued instantly with a task_id, and the finished short URL comes back through your signed webhook or a signed link valid for 24 hours — the same delivery model as the rest of our developer utilities, so you're not learning a new pattern per endpoint. Once live, the short link resolves through our redirect infrastructure with no separate service to run.
Who reaches for a shortener and why
Marketing teams tracking which channel drove a click. SaaS products generating shareable links for invites or referral programs where a long, parameter-heavy URL would look untrustworthy. Print and offline media — a QR code or a spoken URL is only usable if it's short enough to read aloud or type by hand. Internal tools that need stable, memorable links to dashboards or reports that would otherwise be a wall of query-string characters.
A quick word on why link shortening exists at all
URL shorteners became common as social platforms imposed character limits and marketers needed a way to track clicks without cluttering a post — the earliest widely used services traded brevity for a third-party domain, a compromise that made sense before branded shortening was affordable at scale. Custom-domain shortening removes that compromise entirely.
Where it slots into a pipeline
Because generation is async, you can request thousands of short links from a bulk campaign export or a content migration script without holding a connection open, then process the webhooks as they land. Combine it with the click analytics endpoint if you also want to know what happens after someone clicks — the two are designed to be used together, not as separate products.
What you can do with it
Branded referral links
Generate a short, on-domain link for every user's referral code instead of exposing a long tracking URL with query parameters.
Print and packaging
Put a short, readable URL on packaging or signage where a full link would be too long to fit or too awkward to type by hand.
Campaign tracking by channel
Create a distinct short link per ad channel or newsletter so each one's performance can be measured independently downstream.
Migrating a legacy link structure
Bulk-generate short URLs for thousands of old article slugs during a site migration, processing results as webhooks arrive.
FAQ
Can I use my own domain for the short links?
Yes, that's the core of this API — links are served from a domain you control rather than a shared, generic one.
Is the URL shortener free to use?
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.
How fast do I get the short link back?
The request is accepted immediately with a task_id, and the result arrives via signed webhook or a signed link within the normal processing window — no polling required if you use the webhook.
Can I choose a custom slug instead of a random one?
Yes, you can request a preferred slug; if it's already taken you'll get a clear error rather than a silent substitution.
Do failed requests get billed?
No. Every task gets up to three retries, and you're only charged on success.
What happens to the destination URL data after I'm done?
It's kept only for the active retention period and deleted afterward; it is never used to train models.
How much does it cost per link?
0.002 dollars per request, with no hidden per-click fee for the shortening step itself.
Can I generate short links in bulk?
Yes — because each call is async and non-blocking, you can fire off large batches and let the webhooks arrive as each one completes.
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/dev/short-url \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"input":"…"}'const res = await fetch("https://api.kit.forhosting.com/dev/short-url", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"input": "…"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/dev/short-url",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"input": "…"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/dev/short-url", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"input":"…"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"input":"…"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/dev/short-url", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"input": "…"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "dev.short_url",
"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.
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. |