Skip to content

Get X posting credentials

Let keryx auth x capture a token so keryx publish can post a text-and-image announcement to an X account (spec 0060 D16). X uses standard OAuth 2.0 with PKCE; the durable secret is a refresh token that rotates on every use, and keryx writes the new one back each time it mints an access token. Fifteen minutes, plus whatever X's developer sign-up asks of you.

What you need

  • An X developer account at https://developer.x.com/ with a Project and an App. The free tier allows posting (1,500 posts a month at the time of writing); check its limits page before relying on it.
  • The App's User authentication settings turned on.

1. Set up user authentication on the App

In the developer portal, open the App → User authentication settings → Set up:

  • App permissions: Read and write.
  • Type of App: Web App, Automated App or Bot (confidential client).
  • Callback URI / Redirect URL: https://localhost:3764/ and, if you want the fallbacks, https://localhost:3765/https://localhost:3769/.
  • Website URL: the blog is fine.

Save, then copy the OAuth 2.0 Client ID and Client Secret it shows. platforms.x.client_id (config, non-secret) and X_CLIENT_SECRET (environment, never committed). The Client Secret is shown once.

2. Sign keryx in

keryx auth x

It opens the consent page (or prints the URL), captures the code on the https loopback, swaps it with the client secret and the PKCE verifier for an access token and a refresh token, and stores the refresh token (keychain, else the accounts file). platforms.x.{enabled,redirect_uri} are written. In CI, X_REFRESH_TOKEN in the environment is the token; because it rotates, an unattended runner needs the write-back (auth.writeback.*) or it is dead after one use.

3. What a post looks like there

  • Text, hashtags on a new line, the link on a new line, held to 280 weighted characters together: X wraps every URL in t.co, so a link counts as 23 whatever its length, and approve counts it that way too.
  • Images (up to four, JPEG or PNG under 5 MB) are uploaded through the chunked media flow, so no public URL is needed; alt text is set on each.
  • No video here.

Refresh

keryx auth refresh rotates the refresh token (a refresh mints a new pair) and persists the new one; a rejected token is reported as reauth-required. The publisher itself does the same on every post, so a run that posts also refreshes.