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:

ResponseWhat it means
401 INVALID_API_KEYMissing, unknown or revoked key.
400 UNSUPPORTED_DOMAINThe address is not on a platform this service handles.
400 TELEGRAM_PRIVATEA private Telegram channel or an invite link. Members only; no service can read it from outside.
400 TELEGRAM_STORYA Telegram story, not a post. Stories cannot be fetched.
451 CONTENT_REMOVEDThis address was blocked after a takedown notice. It will not be fetched by any route, including this one.
429 API_DAILY_LIMITThis key used its daily request quota.
429 DAILY_LIMITYour 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.