keryx auth¶
Run a platform's interactive OAuth flow and store its access token, so posting "just works" without hand-cranking the API. Instagram is the first platform.
keryx auth <platform> # e.g. keryx auth instagram
keryx auth instagram --redirect <uri> # pin a specific registered redirect
keryx auth instagram --redirect <uri> --code <code> # exchange a pasted code (non-interactive)
Instagram¶
keryx auth instagram runs the Instagram Login OAuth flow — no --redirect
needed in the common case:
- Auto-selects a free loopback port from the registered range
(
https://localhost:3764/…:3769/) and prints the chosen callback. - Prints (and tries to open) the authorize URL with the scopes keryx needs,
including
instagram_business_content_publish. - Captures the returned
codefrom whichever comes first: - a loopback callback server bound to all interfaces on the chosen
port — so the browser reaches it via
localhost, an SSH port-forward, or the host's LAN IP. It auto-captures the code (nothing to copy). Meta requires an https redirect, so the server serves TLS with an in-memory self-signed cert; the browser warns once that the cert is untrusted — proceeding is safe (a local-only, throwaway cert). See Certificate trust. - a code pasted on stdin — the fallback for a box the browser can't reach. Both paths run together; first to arrive wins.
- Exchanges
code → short-lived → long-lived(~60-day) token. - Stores the token: the OS keychain on a desktop, else the config
file (a headless dev server / CI has no keychain). Writes
platforms.instagram.user_id,redirect_uri+enabled. The token is never printed.
Flags¶
--redirect <uri>— pin a specific OAuth redirect URI instead of auto-selecting. Must be one of the URIs registered in the Meta app. Use this to force a port, or for a non-loopback redirect.--code <code>— skip the interactive step and exchange acodeyou copied from the browser's address bar directly. Pair with--redirectset to the same URI the code was issued for.
Prerequisites¶
platforms.instagram.app_idset (the Instagram app ID).INSTAGRAM_APP_SECRETin the environment (the Instagram app secret).- The loopback redirect URIs registered in the Meta app (API setup with Instagram
login → Business login settings). Meta rejects every non-
httpsredirect, regardless of host, so register thehttpsrangehttps://localhost:3764/…https://localhost:3769/. keryx picks the first free one; the range must stay in sync withloopbackPortRangein the code.
Typical run¶
keryx auth instagram
# → Using OAuth callback https://localhost:3764/
# → opens the authorize URL; approve consent (lists content publishing)
# → browser lands on https://localhost:3764/?code=… ; click through the
# one-time cert warning; keryx captures + stores the token automatically.
On a remote box, port-forward first (ssh -L 3764:localhost:3764 …) or just hit
the host's LAN IP — the callback binds all interfaces.
Manual fallback (--code)¶
If the browser can't reach the callback at all, keryx also prints the URL and waits for a pasted code:
- Open the authorize URL, approve, and let the browser land on
https://localhost:<port>/?code=…#_(it may show connection refused — thecodeis still in the address bar). - Copy the
code(betweencode=and#_) and runkeryx auth instagram --redirect https://localhost:<port>/ --code <code>, using the same port the authorize URL used.
The code is single-use and expires in minutes — grab it and exchange promptly.
Certificate trust¶
Today the callback presents an in-memory self-signed cert, so the browser
shows a one-time "untrusted certificate" warning — safe to click through, since
the cert is local-only and regenerated each run. A future (pre-v1) enhancement
will add a keryx init hook to install a phpboyscout-signed trusted root CA
(opt-in), so the callback cert is trusted and the warning disappears.
See the how-to: Get Instagram posting credentials.
Token resolution¶
The Instagram publisher reads its token in this order: INSTAGRAM_ACCESS_TOKEN
env (CI / manual override) → OS keychain → config file. auth refresh
(rotate before the ~60-day expiry, alert on failure) is the next piece.
YouTube¶
keryx auth youtube runs Google's OAuth flow and stores a refresh token (the
durable secret; access tokens are minted from it on demand). It reuses the same
capture machinery (pkg/oauth) as Instagram, but Google is RFC 8252-compliant,
so the callback is a plain-http loopback on any free port — no cert, no
warning, and nothing to pre-register.
- Auto-selects a free ephemeral
http://127.0.0.1:<port>/callback (or use--redirectto pin one). - Opens (or prints) the authorize URL with
access_type=offline+ consent prompt + PKCE so Google returns a refresh token. - Captures the
code(loopback or stdin paste), exchanges it (x/oauth2), and stores the refresh token: keychain on a desktop, else the config file. Writesplatforms.youtube.enabled(+channel_idwhen readable). Never prints the token.
Prerequisites¶
platforms.youtube.client_idset — the Google OAuth Desktop app client id.YOUTUBE_CLIENT_SECRETin the environment — the client secret.- The OAuth app published to "In production" (else refresh tokens expire in 7 days). Uploads stay private until the app passes Google's audit. See the how-to: Get YouTube posting credentials.
Token resolution¶
The YouTube publisher reads its refresh token YOUTUBE_REFRESH_TOKEN env →
OS keychain → config file, and mints access tokens from it via x/oauth2
(YouTube's refresh token doesn't rotate, so refresh = keep it used).
TikTok¶
keryx auth tiktok runs TikTok's OAuth flow and stores a refresh token (the
durable secret). As a Desktop app, keryx uses a loopback redirect, so it
auto-captures the code on a free http://127.0.0.1:<port>/ callback — the
same flow as YouTube — with stdin paste as the headless fallback. (Only TikTok
Web apps are barred from loopback; Desktop apps require it.)
- Auto-selects a free
http://127.0.0.1:<port>/callback/redirect (or use--redirectto pin one) and prints/opens the authorize URL with PKCE — note TikTok's quirks: the params useclient_key(notclient_id) and the PKCEcode_challengeis hex-encoded SHA256 (not base64url), methodS256. - Captures the
code(loopback or stdin paste) — no hosted page needed. - Exchanges it at TikTok's bespoke token endpoint and stores the refresh token:
keychain on a desktop, else the config file. Writes
platforms.tiktok.{open_id, enabled}. Never prints the token.
Prerequisites¶
platforms.tiktok.client_keyset — the TikTok app client key.TIKTOK_CLIENT_SECRETin the environment — the client secret.- A Desktop loopback redirect registered (e.g.
http://127.0.0.1:*/callback/, wildcard port), scopesuser.info.basic+video.publish, and Direct Post enabled. Posts areSELF_ONLYuntil the app passes TikTok's audit. See the how-to: Get TikTok posting credentials.
Token resolution — the rotating refresh token¶
The TikTok publisher reads its refresh token TIKTOK_REFRESH_TOKEN env →
OS keychain → config file. TikTok access tokens last ~24h, so every
post mints a fresh one from the refresh token — and TikTok may rotate the
refresh token on that call, so keryx persists whatever comes back
immediately (a dropped rotation locks the account out). This per-post
mint-and-persist is the concrete shape auth refresh generalises across
platforms.
LinkedIn¶
keryx auth linkedin runs LinkedIn's OAuth flow and stores the access token
(~60 days). LinkedIn is a standard OAuth2 provider (client_id/client_secret),
so it reuses the loopback capture (plain-http, like YouTube) and x/oauth2.
It also reads the member's Person URN from /v2/userinfo (the post author).
- Auto-selects a free
http://127.0.0.1:<port>/redirect (or use--redirect; if LinkedIn's portal rejects http, pin an https one). - Approve the permissions; keryx captures the
code(loopback or paste), exchanges it, reads the Person URN, and stores the access token (keychain, else config) + its expiry. Writesplatforms.linkedin.{author_urn, enabled}. Never prints the token.
Prerequisites¶
platforms.linkedin.client_idset;LINKEDIN_CLIENT_SECRETin the environment.- App on a verified Company Page with "Share on LinkedIn" +
"Sign In with LinkedIn using OpenID Connect" products; scopes
w_member_social+openid+profile. See the how-to: Get LinkedIn posting credentials.
Token resolution — the no-refresh case¶
The LinkedIn publisher reads its access token LINKEDIN_ACCESS_TOKEN env →
OS keychain → config file, alongside a stored expiry. A standard app
gets no refresh token (refresh is Marketing-Developer-Platform-only), so when
the ~60-day token expires keryx cannot auto-refresh — it fails with a clear
"re-run keryx auth linkedin" error, and auth refresh alerts before expiry
(R-AUTH-4) rather than rotating. (If an MDP app ever returns a refresh_token,
keryx stores and uses it automatically.) This is the fourth and final token shape
auth refresh must handle.