KUMODeck
日本語

Sharing on X

Web apps and games spread on X. A post works when three things happen: people see a big image, they tap and try it right away inside the X app, and they post again with their own result or score. KUMODeck gives you the backend pieces for that loop. Your app, your URL, your card — KUMODeck stays invisible to your users (called players in the API).

Every tool is off by default. Turn on only what you want in kumo.config.json → share (reference). KUMODeck never changes your pages: the card tags go into your <head> (you or your AI agent write them there, see Card images and tags).

The fastest way: kumodeck share on#

kumodeck share on          # in your project folder, after your first deploy

One command turns on what a post needs — share.images (the card title comes from your index.html <title>), share.links and share.tracking — writes them into kumo.config.json and pushes it to both environments (--env production for one). Texts and colors you already set are kept. Then put the card tags into your <head> once (kumodeck share tags prints them, see below) and deploy. Sign in with X stays off; turn it on separately if you want it. From your AI agent, the MCP tool share_enable does the same.

ToolConfigKUMODeck-hosted appApp on your own server
Card images (1200×600)share.imagesyesyes (image URL on the API)
Card tags in your HTMLshare.tags (card text and image)you put them in <head> (kumodeck share tags)you put them in <head> (kumodeck share tags)
Share / challenge linksshare.linksyesyes
Traffic per postshare.trackingyesyes
Sign in with Xauth.providers.xyesyes
X in-app browser helpersshare.inAppBrowseryesyes

Share a score or a challenge#

// call from a click handler (the post window must open during the click)
shareButton.onclick = () => kumo.share({ kind: 'challenge', score: best, showName: true });

kumo.share() creates a share id, adds ?ks=<id> to the current page URL and opens X's post screen (https://x.com/intent/tweet, no API key, no cost). The score comes from your app, so the other side sees verified: false — a modified client could send any number. If share.links is off, the post screen still opens, just without a share id.

On the receiving side:

const challenge = await kumo.share.incoming();   // null unless the page was opened from a share link
if (challenge?.kind === 'challenge') showBeatThis(challenge.score, challenge.displayName);

Card images and tags#

X shows a large image when the page it links to has card tags (twitter:card, twitter:image, Open Graph). X's crawler does not run JavaScript, so the tags must be in the HTML the server returns.

KUMODeck does not add anything to your pages. You (or your AI agent) put the tags in the <head> of your HTML:

kumodeck share tags        # prints the <meta> tags to put in <head> (--env production for the live app)

Copy them as printed, into the <head> of every page that people share (for a single-page app: index.html), replacing any og: / twitter: tags already there, and deploy. Keep the image URLs as they are. Asking your agent is enough: "Put KUMODeck's X card tags in the <head> of every page of this app. Get them with kumodeck share tags and do not change the image URLs."

  • The default card (your title, colors and image) works everywhere, including a static site.
  • A card for each share (the score, or challenge of a ?ks= link) needs HTML made on each request: a server-rendered app or your own server reads GET /v1/share/tags?ks=<id> (publishable key) for that request and puts the returned tags in <head> — at least <meta property="og:image" content="https://<your domain>/.card/<id>.png">. When KUMODeck hosts a server-rendered app, the images at /.card.png and /.card/<id>.png on your domain are answered by KUMODeck before your app (custom domain too), but the tags are yours to write: KUMODeck never rewrites your app's HTML. A static site shows the default card for every share link; the link itself, the challenge in the page (kumo.share.incoming()) and the traffic per post still work.
  • Images come from your app's own domain when KUMODeck hosts it (/.card.png, /.card/<id>.png). With a custom domain (play.mygame.com), the image URLs and the share links use that domain — nothing in the post points to KUMODeck.

The rendered card uses your share.images.title, colors and optional background image. The built-in font is Latin only; set share.images.font to a font file in your deployment to draw other scripts. Rendering is metered at cost like the rest of hosting; each card is drawn once and then cached.

Traffic per post#

With share.tracking, the SDK reports when someone opens a share link and when they come back. Only totals are stored per share id and day: visits, new users (first share that brought them), plays (sessions).

kumodeck share link "launch post"      # a tracked URL for your own post
kumodeck share stats --from 2026-10-01

The same numbers are available at GET /v1/admin/share/stats (and from your AI agent through MCP).

Sign in with X#

Register your own X app, paste its Client ID and Client Secret in the dashboard under Sign-in methods → X, and turn on auth.providers.x.enabled (steps: Your own X app). X's consent screen shows your app's name, and X bills your app directly for any API use; KUMODeck does not charge for it. Until an X app is saved, starting Sign in with X fails with 409 provider_not_configured. KUMODeck asks only for users.read tweet.read (no posting, no DMs) and never stores X tokens.

On some KUMODeck environments the dashboard also offers Use without setup, which uses KUMODeck's shared X app instead of yours. X's consent screen then shows the shared app's neutral name, not your app's name.

Sign in with X is free: X does not charge for sign-in (reading the user's own profile, /2/users/me), so KUMODeck counts these calls but prices them at $0. With your own X app, X would bill you directly anyway — and the same X rule applies there.

The user's X name and avatar are not shown to anyone until the user opts in:

await kumo.x.setProfileVisible(true);
// rows: your own list of players (each with a playerId), e.g. read from your Functions
const withX = await kumo.x.withProfiles(rows);   // row.xProfile = { username, name, avatarUrl } or null

Inside X's in-app browser#

Links tapped in the X app open in X's own browser, whose storage is separate from Safari / Chrome.

  • kumo.share.inAppBrowser() returns 'x' there. Sign-in defaults to a redirect instead of a popup.
  • With share.inAppBrowser, kumo.share.openInBrowser() gives a link (valid 10 minutes, once) that continues as the same guest in the user's normal browser. Show it as a button or "copy link"; the link is the user's account, so warn them not to post it.