This guide walks a first-time user from the X Developer Portal to a working md2x draft command.
Official X documentation:
- OAuth2 user access token: https://docs.x.com/fundamentals/authentication/oauth-2-0/user-access-token
- Create draft article: https://docs.x.com/x-api/articles/create-draft-article#create-draft-article
- Publish article: https://docs.x.com/x-api/articles/publish-article
md2x uses OAuth2 Authorization Code Flow with PKCE because X Articles require a user-context token. The App-Only Bearer Token shown under App-Only Authentication is not enough.
Open X Developer Portal, choose your project and app, then open User authentication settings.
Use these values for the first local setup:
App permissions: Read and write
Type of App: Native App or public client with PKCE
Callback URL: http://127.0.0.1:8765/callback
Website URL: any valid URL you control if required
Save the settings and copy the OAuth2 Client ID. Do not use the App-Only Bearer Token for md2x Articles.
md2x config init --client-id YOUR_X_OAUTH2_CLIENT_IDIf a config already exists:
md2x config init --client-id YOUR_X_OAUTH2_CLIENT_ID --forceCheck the effective config:
md2x config showSensitive values are redacted. The config file is:
~/.config/md2x/config.yaml
md2x auth loginmd2x will:
- Generate a PKCE verifier and challenge.
- Start a local callback server at
http://127.0.0.1:8765/callback. - Open the X authorization URL in your browser.
- Exchange the callback
codefor an access token and refresh token. - Save the token locally with
0600permissions.
The browser callback page only means md2x received the authorization callback. Keep the terminal open until it prints that the OAuth token was saved. If the terminal is still running after the callback, md2x is usually exchanging the code with X's token endpoint.
For servers or agent sessions without a browser:
md2x auth login --no-openOpen the printed URL manually, approve access, and let the browser redirect to the callback URL on the same machine.
md2x auth status
md2x auth status --jsonExpected JSON shape:
{
"success": true,
"code": "OK",
"data": {
"profile": "default",
"status": {
"authenticated": true,
"access_token": "<redacted>",
"refresh_token": "<redacted>"
}
}
}Tokens are stored at:
${XDG_STATE_HOME:-~/.local/state}/md2x/auth/default.json
Do not commit this file.
Run offline checks first:
md2x inspect article.md --json
md2x render article.md --format draftjs --jsonThen create the X Article draft:
md2x draft article.md --jsonmd2x uploads local images, converts Markdown into DraftJS content_state, and calls X's create draft article endpoint. It does not publish by default.
Refresh manually:
md2x auth refreshmd2x draft also refreshes an expired native token automatically when a refresh token is available.
Remove local credentials:
md2x auth logoutUse md2x auth status --json before live operations. Treat data.status.authenticated == true as the readiness signal.
For CI, prefer an injected short-lived secret:
X_BEARER_TOKEN=... md2x draft article.md --jsonX_BEARER_TOKEN has the highest priority and is never written to disk by md2x.