Error handling and automatic retries
The Socialift SDK maps every platform's errors onto one hierarchy: AuthError for invalid tokens, PermissionError for missing permissions, RateLimitError for limits, NotFoundError for missing objects and TransportError for network failures. Rate-limit errors, HTTP 429 and 5xx responses and dropped connections are retried automatically by RetryPolicy: 3 attempts by default, exponential backoff from 0.5 seconds, with jitter.
Updated:
Which exceptions should you catch?
Catch the specific classes first and the generic ApiError last. AuthError: send the user to reconnect. PermissionError: fix scopes or App Review — retrying will not help. RateLimitError: try again later using retry_after or your own delay. TransportError: a network problem, usually temporary. License errors (LicenseError) are a separate branch and are not ApiErrors.
Exception hierarchy and attributes
| Class | Parent | Attributes | Meaning |
|---|---|---|---|
SocialiftError | Exception | no | Every SDK error, license errors included |
ConfigurationError | SocialiftError | no | A setting is missing or wrong (for example an empty app_id); raised before any request |
ApiError | SocialiftError | message, provider, status, code, subcode, payload, url | The platform API returned an error |
AuthError | ApiError | same as ApiError | Token is invalid, expired or revoked |
PermissionError | ApiError | same as ApiError | Missing scope, App Review or chat rights |
RateLimitError | ApiError | ApiError + retry_after (seconds or None) | Rate limit — wait and try again |
NotFoundError | ApiError | same as ApiError | The object does not exist or is not visible to the token owner |
TransportError | SocialiftError | url | Connection dropped, DNS or timeout — no HTTP status |
LicenseError | SocialiftError | no | License errors — see the Authentication page |
yesnoScroll the table sideways
Notes and limits
socialift.PermissionError shares its name with Python's built-in PermissionError, so import it under an alias: from socialift import PermissionError as ScopeError. str(exc) looks like [provider] message (HTTP status, code=...).
Which class does a platform error map to?
The SDK classifies errors by these rules; anything that matches none of them stays a plain ApiError.
| Class | Meta (Facebook, Instagram, Ads) | TikTok | Telegram Bot API | |
|---|---|---|---|---|
RateLimitError | code 4, 17, 32, 613 or HTTP 429 | HTTP 429 | rate_limit_exceeded, spam_risk_too_many_posts, spam_risk or HTTP 429 | HTTP 429 or parameters.retry_after in the response |
AuthError | code 190, 102 or HTTP 401 | HTTP 401 | access_token_invalid, access_token_expired, invalid_access_token, token_not_found or HTTP 401 | HTTP 401 or “unauthorized” in the description |
PermissionError | code 10, 200–299 or HTTP 403 | HTTP 403 | scope_not_authorized, scope_permission_missed, unaudited_client_can_only_post_to_private_accounts, url_ownership_unverified, spam_risk_user_banned_from_posting or HTTP 403 | HTTP 403 or “forbidden” in the description (for example, the user blocked the bot) |
NotFoundError | code 803 or HTTP 404 | HTTP 404 | publish_id_not_found, video_not_found or HTTP 404 | HTTP 404 or “not found” in the description |
Notes and limits
Check order: rate limit → auth → permission → not found. For Meta, exc.code is error.code, exc.subcode is error_subcode, and the message comes from error_user_msg when present, otherwise message. For LinkedIn, exc.code is serviceErrorCode. For TikTok the string code wins over the HTTP status, exc.code stays empty, the original code is in exc.payload, and log_id is appended to the message. For Telegram, exc.code is error_code, and the bot token is masked in exc.url.
How are Telegram account (MTProto) errors translated?
TelegramAccount converts Telethon exceptions into the same hierarchy, so one except block covers the bot, the account and the other platforms.
RateLimitErrorFloodWaitErrorand otherFlood…errors;code=420, andretry_afterholds the seconds Telegram asked for. The SDK does not sleep for you, so a 20-minute wait never freezes your app — you decide.AuthErrorAuthKeyUnregisteredError,SessionRevokedError,SessionExpiredError,UserDeactivatedError,AuthKeyDuplicatedError,UnauthorizedError— the session is invalid and the account must be reconnected (status=401).PermissionErrorChatAdminRequiredError,ChatWriteForbiddenError,UserBannedInChannelError,ChannelPrivateError,UserPrivacyRestrictedError,MessageDeleteForbiddenError,AdminsTooMuchError,UserNotParticipantError,ChatRestrictedError(status=403).NotFoundErrorUsernameNotOccupiedError,PeerIdInvalidError,MessageIdInvalidError,ChannelInvalidError,UserIdInvalidError,InviteHashInvalidError, plus theValueErrorTelethon raises when it cannot find an entity (status=404).TransportErrorConnectionError,OSError,TimeoutError— the connection to Telegram was lost.
What do Meta error codes mean?
A Meta Graph API error arrives in an error object with message, type, code, error_subcode, error_user_title, error_user_msg and fbtrace_id. The SDK keeps the whole response in exc.payload, including the fbtrace_id that Meta support asks for.
Per Meta's documentation: 4 is app-level throttling, 17 user-level throttling, 32 page-level throttling in the Pages API, and 613 a custom endpoint limit; 190 means the access token expired or is invalid, 102 is an API session error; 10 means a permission was not granted or was revoked, and 200–299 are specific permission errors.
Meta does not document a standard Retry-After header for throttling; for Business Use Case limits the estimated wait arrives as estimated_time_to_regain_access (in minutes) inside the X-Business-Use-Case-Usage header. That is why retry_after on a Meta RateLimitError is usually None — choose the delay yourself.
Which errors are retried automatically?
In the Facebook, Instagram, Ads, LinkedIn, TikTok and Telegram clients the SDK retries in three cases: RateLimitError; an ApiError with HTTP 429, 500, 502, 503 or 504; and TransportError (aiohttp connection errors and timeouts). AuthError, PermissionError, NotFoundError and other 4xx responses are raised immediately. When attempts run out, the last error is raised.
Delay: if the provider sent a Retry-After header, the SDK waits exactly that long, and this value is not capped by backoff_max. Otherwise the RetryPolicy formula applies. Each retry after an HTTP error is logged at WARNING level on the socialift logger.
Exceptions: when TikTok reports an error inside an HTTP 200 body, it is raised without retries. TelegramAccount (MTProto) does not use RetryPolicy: a FloodWaitError becomes a RateLimitError immediately, and connection attempts are governed by the connection_retries parameter.
Timeouts and connection errors are retried for POST requests too. If the request reached the platform but the response was lost, the action can run twice — for writes where a repeat is harmful, use a separate client with RetryPolicy(attempts=1).
RetryPolicy defaults
| Parameter | Default | Meaning |
|---|---|---|
attempts | 3 | Total attempts, including the first one |
backoff_base | 0.5 | Base of the first delay, seconds |
backoff_max | 30.0 | Upper bound of the delay (before jitter), seconds |
jitter | True | Multiplies the delay by a random factor of 0.5–1.5 |
timeout (client parameter) | 30.0 | Total timeout of a single HTTP request, seconds |
Notes and limits
Formula: min(backoff_base × 2^(n−1), backoff_max), then jitter. With the defaults the SDK waits roughly 0.25–0.75 s after the first failure and 0.5–1.5 s after the second, and the third failure is raised.
How do you pass your own RetryPolicy?
import os
from socialift import Facebook, RetryPolicy, Telegram
# Background jobs: more attempts, longer delays and timeout
patient = RetryPolicy(attempts=5, backoff_base=1.0, backoff_max=60.0)
fb = Facebook.from_env(retry=patient, timeout=60.0)
# Writes where a repeat is harmful: no retries
once = RetryPolicy(attempts=1)
bot = Telegram(token=os.environ["TELEGRAM_BOT_TOKEN"], retry=once)retry= and timeout= are accepted by every HTTP client (Facebook, Instagram, LinkedIn, TikTok, Telegram, Ads), as well as by their from_env() and by Instagram.via_facebook(). The policy is set per client — there is no global setting.
Recommended handling: four cases
Once retries are exhausted, the error reaches your code. Here is a pattern for a background job.
import logging
from socialift import ApiError, AuthError, RateLimitError, TransportError
from socialift import PermissionError as ScopeError
log = logging.getLogger("app")
async def sync_profile(ig, account, jobs):
# account and jobs are your app's objects (a DB record and a job queue)
try:
return await ig.profile(account.token, ig_user_id=account.ig_user_id)
except AuthError:
account.needs_reconnect = True # send the user through OAuth again
except ScopeError as exc:
log.error("Missing permission: %s", exc) # scope or App Review — retrying is pointless
except RateLimitError as exc:
await jobs.retry_later(account.id, delay=exc.retry_after or 300)
except TransportError:
await jobs.retry_later(account.id, delay=30)
except ApiError as exc:
log.error("%s status=%s code=%s", exc.provider, exc.status, exc.code)The except order matters: AuthError, PermissionError and RateLimitError subclass ApiError, so they come first.
The SDK does not refresh an expired token for you. Refresh methods: Instagram.refresh_token (Instagram Login), TikTok.refresh_token, LinkedIn.refresh; for a revoked token the user logs in again.
Error handling checklist
- Do not retry
AuthErrororPermissionError— they need a reconnect or a configuration fix. - When a
RateLimitErrorhas noretry_after, requeue the job with a delay measured in minutes. - Log
str(exc),exc.status,exc.codeandexc.payload. Do not logexc.url: for Meta requests it contains theaccess_tokenquery parameter. - Catch
LicenseErrorseparately fromApiError— it is a license problem, not a platform one. ConfigurationErrorpoints to a bug in code or environment variables; surface it at startup.
Frequently asked questions
How long should I wait when retry_after is None?
That is normal for Meta, which does not document a standard Retry-After header for throttling. Pick a delay yourself (a few minutes, for example) and requeue the job. The SDK's built-in retries are measured in seconds and are not meant to ride out a long throttle.
Does the SDK refresh the token by itself after an AuthError?
No. The SDK neither stores nor automatically refreshes tokens. Refreshing before expiry, or sending the user back through OAuth, is your application's job. TokenBundle.is_expiring() helps you decide when to refresh.
Why does a PermissionError never succeed on retry?
Because the cause is the permissions, not the request: the needed scope was not requested, the app has not passed the platform's review (Meta App Review, for example), or the account lacks rights in a Telegram chat. Fix the configuration and reconnect the user with the right permissions.
Can I turn retries off completely?
Yes: pass retry=RetryPolicy(attempts=1) to the client. Every error is then raised right after the first attempt, which suits actions where a repeat is harmful, such as publishing a post or sending a message.
Where do I find the TikTok error code?
TikTok returns a string code such as access_token_invalid. The SDK maps it to a class but does not copy it to exc.code: the original code is in exc.payload, and the log_id TikTok support asks for is appended to the message.
What is the difference between TransportError and ApiError?
ApiError means the platform answered with an error, so there is a status and often a code. TransportError means no answer arrived at all (dropped connection, DNS, timeout), so there is no status. TransportError is always retried; ApiError only for rate limits, 429 and 5xx.