For scripts
The API
Three endpoints: start a download, ask how it is going, take the file. It is the same machinery the website runs on — the same extractors, the same worker, the same refusals — so anything the site cannot fetch, the API cannot fetch either.
Getting a key
Sign in, open your account page and create one. It is shown once and stored only as a hash, so a lost key is replaced rather than recovered. Send it as an X-API-Key header. A key needs a verified email address, which today means signing in with Google.
Limits
Your account’s daily allowance is shared between the website and the API — it is one account, not two. On top of that, each key has its own daily request quota, and the per-platform hourly caps that apply to everyone apply here too. Files are deleted 24 hours after they are made, exactly as they are for the website; there is no longer retention for API callers.
Start a download
POST the link. quality is one of best, 4k, 1080p, 720p, 480p; format is mp4 or mp3; add "is_playlist": true to get a whole playlist as one ZIP. The call returns immediately with a task id — nothing is downloaded while you wait.
curl -X POST https://api.saveotter.com/api/v1/developer/download \
-H "X-API-Key: so_live_..." \
-H "Content-Type: application/json" \
-d '{"url": "https://t.me/telegram/399", "quality": "1080p", "format": "mp4"}'The answer:
{"task_id": "3f2a…", "message": "Download queued successfully"}Check on it
Poll every couple of seconds. status runs through queued, analyzing, preparing, downloading and converting to complete or error. On error, error_code is the stable value to branch on (the sentence in error is written for people and may be reworded); polling does not use up your key’s quota.
curl https://api.saveotter.com/api/v1/developer/status/TASK_ID \
-H "X-API-Key: so_live_..."{"task_id": "3f2a…", "status": "downloading", "progress": 42,
"filename": null, "speed": "2.1 MB/s", "eta": "00:18",
"error": null, "error_code": null}Take the file
Once status is complete. The response is the file itself, with its real name in Content-Disposition — curl -OJ saves it under that name. A task belongs to the key that created it: another key gets a 404, not someone else’s file.
curl -OJ https://api.saveotter.com/api/v1/developer/file/TASK_ID \
-H "X-API-Key: so_live_..."What it will refuse
Every refusal is a JSON body with a stable code. These are the ones worth handling:
| Response | What it means |
|---|---|
| 401 INVALID_API_KEY | Missing, unknown or revoked key. |
| 400 UNSUPPORTED_DOMAIN | The address is not on a platform this service handles. |
| 400 TELEGRAM_PRIVATE | A private Telegram channel or an invite link. Members only; no service can read it from outside. |
| 400 TELEGRAM_STORY | A Telegram story, not a post. Stories cannot be fetched. |
| 451 CONTENT_REMOVED | This address was blocked after a takedown notice. It will not be fetched by any route, including this one. |
| 429 API_DAILY_LIMIT | This key used its daily request quota. |
| 429 DAILY_LIMIT | Your account used its daily downloads. |
| 429 <PLATFORM>_LIMIT_* | The hourly cap for that platform, for your account or for the whole service. |
Fair use
No scraping runs, no re-publishing what you fetch, and the same terms that cover the website cover the API. If your key is doing something the service cannot carry, it will be revoked — you will get an email address to argue with on the imprint page, and a straight answer.