Developer Guide

Write a SKILL.md That Posts to X
One File, Every Agent

A SKILL.md is one markdown file that teaches an agent to do one job. Copy the one below into your agent skills directory, set your ot_ key, and the agent can publish and schedule on X. No X developer account.

The file is identical for every agent. Only the directory it goes in changes, and not every agent reads SKILL.md yet.

Last updated: September 2026

7-day free trial. Cancel anytime.

What is a SKILL.md file?

A SKILL.md is a markdown file with two parts: YAML frontmatter that names the skill and says when to use it, and a body that says how to do the work. The agent keeps only the description in mind. When a request matches, it loads the body and follows it.

That makes a posting skill mostly documentation. There is no package to install, no runtime, and nothing to keep running. The whole capability is a file, which is why the same file works across agents that share the convention.

  • Frontmatter is the trigger. The description is what gets matched against the user request, so write it as the situation, not the feature.
  • The body is the API contract. Real endpoints, real field names, real status codes. Anything vague gets guessed at.
  • The rules section is the point. It is where your posting policy lives: confirm before publishing, never fabricate a response, always show the returned id.

Already using Claude Code?

The Claude Code skill page covers the same ground with the MCP path side by side. The background on why one file travels across agents is in the SKILL.md write-up.

Step-by-step: build the skill

1

Get your ot_ API key

Create a key at /developer and connect your X account once. The key starts with ot_ and goes in the Authorization: Bearer header on every call. The full auth model is in the docs, and the quickstart gets you a first post in a couple of minutes.

bash
curl https://opentweet.io/api/v1/me \
  -H "Authorization: Bearer ot_your_key_here"

If that returns your account and subscription.has_access is true, the skill will work.

2

Copy the skill

This is the whole file. Every endpoint and field name below is the live OpenTweet API, the same one the MCP server calls. Save it as SKILL.md.

SKILL.md
---
name: post-to-x
description: Post, schedule, and thread on X (Twitter) through the OpenTweet REST API. Use whenever the user asks to tweet, post to X, schedule a post, or publish a thread.
version: 1.0.0
---

# Post to X with OpenTweet

Post to the user's connected X account through the OpenTweet REST API.
The base URL is https://opentweet.io. The API key lives in the
OPENTWEET_API_KEY environment variable and starts with ot_.

## Authentication

Every request needs these headers:

    Authorization: Bearer $OPENTWEET_API_KEY
    Content-Type: application/json

## Before you post

Check the connection and the remaining quota first:

    GET https://opentweet.io/api/v1/me

Confirm subscription.has_access is true and limits.remaining_posts_today
is above zero before publishing or scheduling.

## Publish now

    POST https://opentweet.io/api/v1/posts
    {"text": "Tweet text here", "publish_now": true, "platforms": ["x"]}

A successful response carries status "posted", a 24-character id, and a
url pointing at the real X post. Report that url. Never construct one.

## Schedule for later

    POST https://opentweet.io/api/v1/posts
    {"text": "Tweet text here", "scheduled_date": "2026-10-01T09:00:00Z", "platforms": ["x"]}

scheduled_date is ISO 8601 and must be in the future. A scheduled post
comes back with status "scheduled".

## Save a draft

    POST https://opentweet.io/api/v1/posts
    {"text": "Tweet text here"}

Omit both publish_now and scheduled_date. Drafts do not require an
active subscription.

## Post a thread

    POST https://opentweet.io/api/v1/posts
    {
      "text": "First tweet of the thread",
      "is_thread": true,
      "thread_tweets": ["Second tweet", "Third tweet"],
      "publish_now": true
    }

Each entry in thread_tweets is its own tweet with its own character budget.

## List recent posts

    GET https://opentweet.io/api/v1/posts?status=scheduled&limit=20

status accepts scheduled, posted, draft, or failed.

## Rules

- 280 characters per tweet, counted per tweet inside a thread.
- Never send publish_now and scheduled_date in the same request.
- Show the user the drafted text and get confirmation before publishing.
  Publishing to X cannot be undone.
- Make the real HTTP call every time. Never answer from memory and never
  invent a response value.
- A 4xx or 5xx status means the operation failed. Say so plainly. Do not
  report success and do not retry blindly.
- After scheduling, show the id from the response. It is a 24-character
  ObjectId. If you cannot show a real one, the call did not happen.
- Omitting platforms does not mean X only. It follows the user's auto
  cross-post setting. Send "platforms": ["x"] when the user said X.
- Read the results array before reporting an outcome. A post can publish
  on one network and fail on another.
- 403 means no subscription or no connected X account. 429 means a rate
  limit or a daily cap. Pro allows 60 requests per minute, 1,000 per day.
- Bulk creates cap at 50 posts per request.
3

Put it where your agent looks

The folder name becomes the skill name, so post-to-x/SKILL.md gives you a skill called post-to-x.

AgentWhere the file goesNotes
Claude Code, one project.claude/skills/post-to-x/SKILL.mdCommitted with the repo
Claude Code, everywhere~/.claude/skills/post-to-x/SKILL.mdAvailable in every project
OpenClaw~/.openclaw/skills/post-to-x/SKILL.mdSame file, different root
Agents that read one instruction fileAGENTS.mdPaste the body in, skip the frontmatter
Everything elseCheck your agent documentationThe file does not change, only the folder

Verify the path in your own agent docs

Skill directory conventions differ per agent and they change. Treat the two paths above as the ones to copy and check the rest against your agent documentation before filing a bug. The file itself is identical everywhere, so a wrong folder is the only thing that usually goes wrong. For OpenClaw specifically, see the OpenClaw guide.
4

Keep the key out of the file

The skill references OPENTWEET_API_KEY by name and never contains the key itself. Export it in your shell, or put it in a gitignored .env file your agent already loads.

bash
export OPENTWEET_API_KEY="ot_your_key_here"

echo 'OPENTWEET_API_KEY=ot_your_key_here' >> .env
echo '.env' >> .gitignore

Skill files get shared

A SKILL.md is committed, copied into other repos, and pasted into issues. A key inside it leaks the first time someone finds it useful. If you have already pasted one in, revoke it at /developer and issue a new one.
5

Ask your agent to post

Nothing to invoke. Ask in plain language and the description in the frontmatter does the matching.

Prompts that trigger the skill
Tweet that the 2.1 release is out, keep it under 200 characters.

Schedule a tweet for tomorrow at 9am UTC about the new pricing page.

Turn my changelog into a three-tweet thread and save it as a draft.

What you should see back: the drafted text, a confirmation request, then a 24-character id and, for a published post, a real url on x.com. If the agent reports success without an id, it did not make the call. That is exactly what the rules section of the skill exists to prevent.

Make it schedule instead of publish

Publishing to X is irreversible, and an agent that can publish on its own will eventually publish something you would have edited. Swap the rules section for this one and the default becomes a scheduled post you can still change.

SKILL.md
## Rules

- Default to scheduled_date. Only use publish_now when the user says
  "post it now" or "publish immediately".
- When no time is given, schedule for the next weekday at 09:00 in the
  user's timezone and tell them the time you picked.
- Show the drafted text and the scheduled time, then wait for a yes.

Scheduled posts stay editable

A scheduled post comes back with status: "scheduled" and sits in your OpenTweet queue until its time. You can edit or delete it from the dashboard right up to the moment it goes out, which turns the agent into a drafting step rather than a publishing risk.

When to use MCP instead

A skill is instructions. MCP is tools. They solve different halves of the problem and they compose, so the honest answer is often both.

Use a SKILL.md

One scoped job, zero dependencies, nothing to install or keep running. The file is plain text you can commit, review in a pull request, and copy between machines. It is also the right place for policy: your voice, your cadence, the rule that says never publish without asking.

Best when posting and scheduling is all you need, or when the agent has no MCP support.

Use the MCP server

Typed tools with validation instead of prose the agent has to interpret. You get the full surface: threads, the evergreen queue, long-form X Articles, media upload, analytics, and multi-account targeting. Connect the hosted server at https://mcp.opentweet.io/mcp.

See the tool reference and post to X over MCP.

Running both

Attach the MCP server for the tools and keep a short skill that only holds rules: which account to post from, what a good tweet looks like for you, and when to ask before sending. The pattern for wiring a posting tool into any agent is in give an MCP agent a Twitter tool. Endpoint reference lives in the docs, and plan limits are on pricing.

Frequently asked questions

How do I write a SKILL.md that posts to X?

Write frontmatter with a name and a description that says when to use the skill, then a body that documents the posting API. Point it at POST https://opentweet.io/api/v1/posts with an Authorization: Bearer header holding your ot_ key, document the text, publish_now, scheduled_date, is_thread, and thread_tweets fields, and finish with rules the agent must follow. The complete file is on this page and you can copy it as is.

Where do I put a SKILL.md file for Claude Code?

For a single project, save it as .claude/skills/post-to-x/SKILL.md in the repository. To make it available in every project, save it as ~/.claude/skills/post-to-x/SKILL.md instead. The directory name becomes the skill name. OpenClaw uses ~/.openclaw/skills/post-to-x/SKILL.md. Directory conventions vary per agent and change often, so check your own agent documentation. The file itself is identical everywhere.

What goes in SKILL.md frontmatter?

At minimum a name and a description. The description is the load-bearing field, because the agent reads only the description until it decides the skill is relevant, and then it loads the body. Write the description as the trigger, for example: post, schedule, and thread on X through the OpenTweet API, use whenever the user asks to tweet or post to X. A version field is optional and useful once you share the file.

Do I need an X developer account for a posting skill?

No. OpenTweet holds the X connection, so the skill only ever calls the OpenTweet API with your ot_ key. You never create an X developer app, never handle OAuth tokens, and never enroll in X API billing. Building the same skill against the X API directly would require all three.

Should I use a SKILL.md or an MCP server?

Use a SKILL.md when you want one scoped job with zero dependencies and a file you can commit, share, and read. Use the OpenTweet MCP server at https://mcp.opentweet.io/mcp when you want the full tool surface: threads, evergreen queue, long-form articles, media upload, analytics, and multi-account targeting as typed tools. They compose. Many people run the MCP server for tools and keep a short skill for posting policy.

How much does it cost for an AI agent to post to X?

OpenTweet is a flat $11.99 per month on the Pro plan, which includes the REST API and MCP access with no per-post fee. Posting through the X API directly instead costs roughly $0.015 per post, or about $0.20 per post when the tweet contains a link, on top of an X developer account.

Give every agent you run a tweet skill

One ot_ key, one markdown file, and any agent that reads skills can publish and schedule on X. No X developer account, no per-post fees.

7-day free trial. Cancel anytime.