Skip to content
← Community packages

Look up X users and tweets, search recent posts, and preview guarded posts through saved OAuth.

Browse files

  • Integrations
  • x
  • twitter
  • social
  • oauth
  • posting
  • search
  • tweets
  • multi-account
License
MIT
Published
August 22, 2026
Pinned commit
e68da93
Rating
No ratings yet
Forks
0
Stars
0
Adaptation effort

README

@kody/x

Official X logo (black rounded square, white glyph) from the X brand toolkit. Paths are unmodified. X and the X logo are trademarks of X Corp.

Intent

Give any Kody account reusable, headless X API v2 helpers for user lookup, tweets, recent search, and guarded posting. Auth is the caller’s saved x / x-* OAuth integration (optional app-only bearer for public reads). Identity always comes from the connected account — never a hard-coded profile.

This listing is meant to be forked. After you fork, connect your X app and run ./smoke-test on your copy. Do not treat the live @kody/x integration as yours. Share https://kody.codes/@kody/x.

When To Use

  • Verify a saved x OAuth integration and read the connected user
  • Look up a user by username, read a tweet, list a user’s recent tweets, or search recent posts
  • Call uncommon X API v2 routes with ./request
  • Preview a post or social action with dryRun: true, then mutate only after confirm: true

Do not use this package as a 24/7 bot, a scraping client, or a place to paste access tokens. It does not include encrypted X Chat, Fly sidecars, or any owner-specific infrastructure.

Agent setup

X is bring-your-own OAuth. There is no built-in Kody X app. Tokens come from the hosted connect page. Never paste a client secret or access token into chat.

  1. Open X Developer Portal → create a Project and App with User authentication (OAuth 2.0).
  2. App type: Native App (public / PKCE) or a confidential client with PKCE enabled.
  3. Callback / redirect URI — register exactly https://kody.codes/connect/oauth (the connect page shows the same value with a copy button).
  4. Website URL can be https://kody.codes.
  5. Open this prefilled connect URL while signed in to Kody:
https://kody.codes/connect/oauth?provider=x

First-time setup also needs X’s authorize and token URLs. Use this complete prefilled URL when the connection does not exist yet:

https://kody.codes/connect/oauth?provider=x&authorizeUrl=https%3A%2F%2Fx.com%2Fi%2Foauth2%2Fauthorize&tokenUrl=https%3A%2F%2Fapi.x.com%2F2%2Foauth2%2Ftoken&apiBaseUrl=https%3A%2F%2Fapi.x.com%2F2&flow=pkce&allowedHosts=api.x.com%2Cupload.x.com&dashboardUrl=https%3A%2F%2Fdeveloper.x.com%2Fen%2Fportal%2Fdashboard&scopes=tweet.read%20tweet.write%20users.read%20offline.access%20like.read%20like.write%20follows.read%20follows.write%20bookmark.read%20bookmark.write

Decoded:

  • Redirect / callback: https://kody.codes/connect/oauth
  • Authorize: https://x.com/i/oauth2/authorize
  • Token: https://api.x.com/2/oauth2/token
  • API base: https://api.x.com/2
  • Flow: pkce (add flow=confidential&pkce=true if the X app has a client secret)
  • Hosts: api.x.com, upload.x.com
  • Scopes: tweet.read, tweet.write, users.read, offline.access, like.read, like.write, follows.read, follows.write, bookmark.read, bookmark.write
  1. Paste the Client ID (and Client Secret only for confidential apps) into the Kody form, continue to X, and approve.
  2. Run Smoke test on the forked package.

Reconnect the same connection with https://kody.codes/connect/oauth?provider=x. Kody reuses the saved authorize/token endpoints.

Optional app-only bearer

Public reads can use an app-only bearer token instead of user context. Save it as xBearerToken (never paste the value into chat) and pass authMode: 'bearer' on read helpers:

https://kody.codes/account/secrets/new?name=xBearerToken&description=X%20API%20v2%20app-only%20bearer%20token%20for%20optional%20public%20reads&allowedHosts=api.x.com&scope=user

OAuth remains the default. Writes always use OAuth.

Multi-account

OAuth helpers accept optional account / integration:

CallResolves to
omit bothintegration x
account: 'work'integration x-work
integration: 'x-work'that exact integration

Connect more accounts at https://kody.codes/connect/oauth?provider=x-work (same first-time query params, different provider). ./accounts lists whatever x / x-* integrations the signed-in user has connected.

Scopes

ScopeUsed by
tweet.readtweet lookup, user tweets, search
tweet.writecreate / delete tweet, repost
users.read./get-me, user lookup, follow
offline.accessrefresh tokens
like.read / like.writelike / unlike
follows.read / follows.writefollow / unfollow
bookmark.read / bookmark.writebookmark helpers

./request uses whatever scopes the saved token already has.

Smoke test

After connect or reconnect, import the published package (not packages.invoke):

import smokeTest from 'kody:@kody/x/smoke-test'

export default async function main() {
	return await smokeTest()
}

Success looks like { ok: true, integration: 'x', user: { id, username, name } } and does not post. Missing credentials return { ok: false, setup } with the prefilled connect URLs. HTTP 429 counts as auth plumbing verified.

Low-level equivalent (execute exploration only):

import { createAuthenticatedFetch } from 'kody:runtime'

export default async function main() {
	const xFetch = await createAuthenticatedFetch('x')
	const response = await xFetch('https://api.x.com/2/users/me')
	if (!response.ok) {
		throw new Error('X users/me failed: ' + response.status)
	}
	const data = (await response.json()) as { data?: { id?: string; username?: string } }
	return { ok: true, hasUser: Boolean(data.data?.id) }
}

dryRun and posting

Mutating helpers never write to X when dryRun: true, and they refuse a live write unless confirm: true after the user approved the exact payload.

import createTweet from 'kody:@kody/x/create-tweet'

export default async function main() {
	return await createTweet({ text: 'Draft text', dryRun: true })
}

Do not infer confirmation from a successful dry-run. Require a fresh confirm: true for the exact text or target.

Exports

ExportDescription
.Package overview, connect URLs, and export map
./accountsList connected x / x-* integrations
./smoke-testRead-only OAuth /users/me check
./requestGeneric X API v2 helper; writes need dryRun / confirm
./refresh-oauth-tokenRotate tokens through the host refresh helper
./get-meAuthenticated user profile
./get-user-by-usernamePublic user lookup
./get-tweetRead one tweet by id
./search-recentSearch recent public posts
./get-user-tweetsRecent tweets for a user id
./create-tweetPreview or create a post
./delete-tweetPreview or delete a post
./like-tweet / ./unlike-tweetPreview or like / unlike
./repost-tweet / ./unrepost-tweetPreview or repost / unrepost
./bookmark-tweet / ./remove-bookmarkPreview or bookmark
./follow-user / ./unfollow-userPreview or follow / unfollow
./media-upload-guideStatic media upload notes

Examples

import listXAccounts from 'kody:@kody/x/accounts'
import getMe from 'kody:@kody/x/get-me'
import createTweet from 'kody:@kody/x/create-tweet'

export default async function main() {
	const { accounts } = await listXAccounts()
	const me = await getMe()
	const draft = await createTweet({
		text: 'Hello from Kody',
		dryRun: true,
	})
	return { accounts, me: me.data, draft }
}

Named account:

import getMe from 'kody:@kody/x/get-me'

export default async function main() {
	return await getMe({ account: 'work' })
}

Troubleshooting

  • Connect page asks for authorize/token URLs: use the complete first-time URL in Agent setup.
  • HTTP 401 / expired token: reconnect at https://kody.codes/connect/oauth?provider=x, or call ./refresh-oauth-token.
  • HTTP 403 on writes: the X app needs the matching write scopes. Reconnect after adding them.
  • HTTP 429: X rate-limited the call. Treat as plumbing-verified for smoke; wait before retrying.
  • redirect_uri mismatch: the X app callback must be exactly https://kody.codes/connect/oauth.

Docs

Report this listing

Log in to report this listing.