Schedule unattended token refresh¶
Keep unattended posting alive. keryx auth refresh refreshes/rotates the stored
platform tokens and alerts when it can't — run it from a scheduled pipeline in the
owning project so a token never goes silently stale (the #1 unattended failure mode;
spec 0010-auth-refresh.md).
keryx is stateless and ships no schedule — the schedule lives in your project's CI
(the same repo that holds your content and runs keryx post due). This guide wires that
up on GitLab.
Prerequisite: you've already captured each platform's token with
keryx auth <platform>— see the credential guides for Instagram, YouTube, TikTok, and LinkedIn.
What refresh does per platform¶
auth refresh all fans out across your enabled platforms, runs each independently,
reports per-platform, and exits non-zero if any needs attention (so the pipeline goes
red):
| Platform | Action | Persists? | Alerts when |
|---|---|---|---|
| refresh the long-lived token in place (near its ~60-day expiry) | new access token | refresh fails / token already dead | |
| YouTube | health-check the durable refresh token | nothing (durable) | refresh token revoked/invalid |
| TikTok | rotate — exchange for a new access and refresh token | new refresh token immediately | rotation fails (old refresh dead) |
| none possible (standard app has no refresh) | — | re-auth due within 14 days |
TikTok is the one that must write its rotated refresh token back, or the next
scheduled run is locked out — which is what the gitlab write-back backend (below) is
for.
1. Preview it first (--dry-run)¶
--dry-run reports what each platform's token looks like and the action it would take,
and changes nothing:
$ keryx auth refresh all --dry-run
instagram refreshed expires — would refresh the long-lived token in place
tiktok rotated expires — would rotate the refresh token
youtube healthy expires — would health-check the refresh token
You can scope to one platform — keryx auth refresh tiktok --dry-run. An unknown name
errors (no refresher for "…", exit 1).
2. Choose the write-back backend¶
Locally, refreshed tokens persist via keychain/config. In CI there's no keychain, and a
job can't natively write back to the CI variables it read — so select the gitlab
backend, which updates the CI/CD variables over the API:
# keryx config (committed — non-secret)
auth:
writeback:
backend: gitlab # local (default) | gitlab
gitlab:
# project + api_url default to CI_PROJECT_ID / CI_API_V4_URL inside a pipeline;
# set them explicitly only when running outside CI.
# project: "12345678"
# api_url: "https://gitlab.com/api/v4"
The backend updates the same masked, protected variables the adapters read, keyed by
each token's variable name (e.g. TIKTOK_REFRESH_TOKEN). It only ever updates an
existing variable's value — it never creates a new, unmasked secret.
3. Set the CI/CD variables¶
In the owning project → Settings → CI/CD → Variables, all Masked and Protected:
| Variable | Why | Written back? |
|---|---|---|
TIKTOK_REFRESH_TOKEN |
TikTok's rotating refresh token | yes — every run |
INSTAGRAM_ACCESS_TOKEN |
Instagram long-lived token | yes, near expiry |
YOUTUBE_REFRESH_TOKEN |
YouTube durable refresh token | no (health-check only) |
LINKEDIN_ACCESS_TOKEN |
LinkedIn access token | no (standard app can't refresh) |
LINKEDIN_REFRESH_TOKEN |
only if you're a LinkedIn MDP partner | yes (MDP only) |
GITLAB_TOKEN |
api-scoped project/group access token for write-back | — |
GITLAB_TOKENmust have theapiscope —CI_JOB_TOKENcannot write CI/CD variables. Make it a project or group access token and mark it Protected.CI_PROJECT_IDandCI_API_V4_URLare predefined by GitLab; you don't set those.
Plus each platform's existing client secrets/ids (TIKTOK_CLIENT_SECRET,
YOUTUBE_CLIENT_ID / YOUTUBE_CLIENT_SECRET, INSTAGRAM_APP_SECRET /
INSTAGRAM_USER_ID, LINKEDIN_CLIENT_SECRET) as set up in the credential guides.
4. Configure alerts¶
Any failure (or an impending non-refreshable expiry) must reach a human. The red pipeline is the always-on baseline; add a notifier for an active ping:
auth:
alerts:
backend: webhook # none (default) | webhook | email
webhook:
url: "" # or the ALERT_WEBHOOK_URL variable (Slack/Teams incoming webhook)
# email:
# host: smtp.example.com
# port: 587 # default
# from: [email protected]
# to: [[email protected]]
Secrets come from the environment: ALERT_WEBHOOK_URL, or ALERT_SMTP_USERNAME /
ALERT_SMTP_PASSWORD for email.
5. Add the scheduled job¶
In the owning project's .gitlab-ci.yml:
token-refresh:
stage: maintenance
image: registry.gitlab.com/phpboyscout/keryx:latest # or however you install keryx
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule" && $SCHEDULE_TASK == "token-refresh"'
script:
- keryx auth refresh all
# GITLAB_TOKEN, TIKTOK_REFRESH_TOKEN, … come from the protected CI/CD variables above.
Then CI/CD → Schedules → New schedule:
- Interval: weekly (see cadence below).
- Variable:
SCHEDULE_TASK=token-refresh(so this schedule runs only this job). - Target the protected default branch (the variables are Protected).
A failing run turns the schedule red and fires your notifier; a green run means every enabled platform is fresh (and TikTok's rotated token has been written back for next time).
Cadence — why weekly¶
Weekly sits comfortably inside every window: it keeps Instagram's ~60-day token fresh, rotates TikTok well within its ~365-day refresh wall, health-checks YouTube's durable token, and gives ~7 weeks of warning before a LinkedIn token expires (the alert fires at 14 days). More frequent is harmless; much less frequent risks missing the LinkedIn re-auth window.
When an alert fires¶
- Instagram / TikTok / YouTube "refresh failed" — the refresh/rotate/health-check
didn't work; the token is likely revoked or dead. Re-capture it:
keryx auth <platform>, then update the CI variable. - LinkedIn "re-auth due" — a standard LinkedIn app can't refresh; run
keryx auth linkedinbefore the date in the alert and updateLINKEDIN_ACCESS_TOKEN.
See also¶
- Schedule unattended posting — the
post dueside of the same scheduled-pipeline story. - Spec
0010-auth-refresh.md— the design, the per-platform token shapes, and the pluggable write-back/notifier backends.