The TinyURL migration importer takes an API token, a target Elido domain and a conflict strategy, then copies your TinyURL links into Elido as one background job. It has one serious known defect: it calls GET /aliases on api.tinyurl.com, and that path is not in TinyURL's published API. This post covers what the importer does, what we have verified, and what we have not.
If you want the how-to for planning a move off TinyURL (what can be redirected, CSV mapping, cutover checklist), read Migrate from TinyURL. This is the engineering side.
The known bug
The importer's page fetch builds this request:
GET https://api.tinyurl.com/aliases?page=1&per_page=100
Authorization: Bearer <token>
and expects {"data": [{alias, url, title, tags}], "meta": {"total", "has_more"}} back.
TinyURL's OpenAPI spec (accessed 2026-10-02) defines no /aliases path. Listing is GET /urls/{type}, where type is available or archived, with optional from, to and search (an alias: or tag: prefix) filters. The spec defines no page or per_page parameters and no rate limits. Alias operations live under /alias/{domain}/{alias}.
So the pagination contract in our code was never taken from TinyURL. Our unit test serves a hand-written response in that shape and passes, which proves the parser matches our assumption and nothing about TinyURL. We have not run the importer against a live TinyURL account, because that needs a paid-plan token. Until we do, do not plan a migration around it. The fix is tracked in our internal backlog: switch to /urls/available (and /urls/archived behind a flag), page through from/to windows, parse defensively, and test against a real captured response. Whether the real response carries a list or a single object is something the spec leaves unclear.
What the worker does after it gets a page
The integrations page in a DEMO workspace with synthetic data. The TinyURL migration is started from here.
Everything past the HTTP call is independent of the endpoint question. This part we can state from the code.
- Start.
POSTwithtoken,target_domain_idand optionalconflict_strategy. The handler rejects an empty token, a missing domain and any strategy outsidesuffix,skip,fail, then answers202with the job and runs the worker in a background goroutine detached from the request. - Per link. A row without a URL or without an alias counts as failed with a reason. Otherwise the alias becomes the slug, destination and title are copied (title truncated to 200 characters), and tags are copied with
imported:tinyurlappended. - Progress. Counters flush to the job row every 50 links. At most 1,000 error rows are logged per job.
- Auth and rate errors. A 401 or 403 fails the job with a "check the bearer token" message, a 429 fails it as rate-limited. There is no retry or backoff: the job stops.
Conflict rules
The links list in a DEMO workspace. Imported links carry the imported:tinyurl tag, so you can filter a batch for review.
Before each insert the worker does one indexed lookup for (domain, slug).
- suffix (default) tries
alias-2,alias-3, up toalias-50, and fails the row if all are taken. - skip leaves the existing Elido link alone and logs the source row.
- fail fails the job at the first conflict.
TinyURL calls these aliases, Elido calls them slugs. Same thing: the characters after the host. If your target domain is empty, aliases carry over unchanged.
Caps
One shared set of constants, used by every migration vendor: 50,000 links per run, a 30-minute budget, progress every 50 links, 1,000 logged errors.
Two behaviours are worth knowing. When the 50,000 cap is hit, the loop simply stops and the job completes; it does not fail and does not tell you to contact anyone. Compare the final counts with your source total. When the 30-minute budget runs out, the job is marked failed with the number imported so far.
The worker lives inside api-core. A deploy or crash mid-run kills it, and a sweep that runs every five minutes flips jobs with no progress for 30 minutes to failed. There is no saved cursor, so you restart the job. Re-running is safe under suffix or skip, though under suffix it will create -2 copies of links the first run already wrote, so prefer skip for a re-run.
Token handling
The token goes from the request body to the goroutine and nowhere else. The job row records no token, and nothing is encrypted at rest because nothing is stored. The request body is read with a 4 KB limit.
What the code does not do: it does not validate the token before starting, and it does not check the plan. A bad token surfaces as a failed job after the first request, not as a form error. Earlier versions of this post described a preflight step against a domains endpoint. That step does not exist in the code, and TinyURL's spec has no such path either.
What is not migrated
- Click history. The importer reads link fields only. New clicks count from the moment a link is live in Elido.
- QR styling, templates, branded-domain settings. Not read. A branded hostname is a separate step: add it as an Elido domain and change DNS, covered in custom domains for short links.
- Links without an account. No token can list them.
The importer does not create the domain for you, does not handle a TinyURL domain field, and has no CSV upload of its own. For file-based moves use the bulk import form or the walkthrough in bulk import from Google Sheets.
Testing it and working around it
Two practical sections follow. Testing the importer. Moving without it.
A safe way to check an import
Whenever the endpoint is fixed, or if you test the importer yourself before then, do it small. Create a throwaway workspace, add an empty domain, and run the job with the fail strategy first. An empty domain means no conflicts, so any failure is a parsing or auth problem rather than a collision. Then compare three numbers: the source count in TinyURL, the total the job reports, and the imported plus skipped plus failed counters. Because the worker takes meta.total from the response it expects, a mismatch between total and your real count is the first sign that the response shape differs from what the parser assumes.
Then filter the links list by imported:tinyurl and open ten links at random. Check the destination, the slug and the title against TinyURL. After that, switch to skip for any re-run.
How to export from TinyURL without the importer
The documented listing call is GET /urls/available with a bearer token, and GET /urls/archived for archived links. Narrow the result with from and to datetimes if the account is large, since the spec gives no page parameters. Write the output to a file before you do anything else, reduce it to destination, slug, title, and load it with the bulk form. Details of that mapping are in the how-to guide. We have not verified the live response shape either, so inspect one response by hand before scripting against it.
What happens next
The sequence is: fix the endpoint, add a real captured response as a test fixture, then re-check every figure in the how-to guide and on the migration landing page. Pagination and the real per-request limit need re-measuring against a live account, since the spec gives neither. If you need to move off TinyURL before that, compare options in Elido vs TinyURL and TinyURL alternatives, and use the CSV route.
Related on the blog
Frequently asked questions
Does the Elido TinyURL importer work today?
Treat it as unverified. The worker requests GET /aliases?page=N&per_page=100 on api.tinyurl.com. TinyURL's OpenAPI spec (accessed 2026-10-02) has no /aliases path; links are listed by GET /urls/{type}. The shipped unit test only checks the response shape we assumed. A fix is on our backlog, and until it lands the dependable route is a CSV or bulk paste.
What does the importer copy from each TinyURL link?
Destination URL, alias (used as the slug), title and tags, plus an imported:tinyurl tag on every link it creates. It does not copy click history, QR styling or anything else.
What happens when an alias already exists in Elido?
You pick a strategy per job. Suffix tries alias-2, alias-3 and so on up to alias-50, skip leaves the existing link and logs the row, fail marks the job failed at the first conflict. The default is suffix.
Is my TinyURL token stored?
No. The start request carries the token to a background goroutine, and the job row stores no token reference. The token exists in process memory for the run.
Is there a limit on how many links one import handles?
Yes. A run stops after 50,000 links or 30 minutes, whichever comes first. At the link cap the job finishes as completed with the first 50,000 handled, so check the counts against your source.
Can I migrate from a free TinyURL account?
TinyURL ties link listing to an API token, and links made without signing in belong to no account, so there is nothing to list. Rebuild a destination list yourself and use bulk import.
Try Elido
Paste a URL, get a working short link
No signup. Link lives for 30 days. Sign up to keep it forever.
Free, no signup required · 2 per day