How do you send a DM with the Twitter API?
Last updated August 3, 2026
You register the X session you want to act as, then POST a recipient_id and text to the send route. The recipient must be a numeric user id, not a handle. Sends are capped at 150 an hour per key as a sliding window, each direct-message call bills at $0.0016, and a refused send is never billed.
Every rate here is the pricing TwitterAPIs publishes, $0.0008 per call and $0.04 per 1,000 tweets (source: twitterapis.com/pricing).
Why does a DM need a session when a read does not?
Because a direct message is not public data. A read of a public timeline needs nothing but your key, while a message belongs to a mailbox, so the call has to run as the account that owns it. You register that account once by handing over the auth_token and ct0 cookies from a logged-in session, or you pass them as headers on each call and nothing is stored.
What happens if no session is registered?
Every message call answers 409 with a session_required error, which is deliberate rather than a fault. The same code appears in a second situation worth knowing about: reaching for a conversation the registered account is not part of returns 401 and deregisters the session, after which everything answers 409 until you register again. Treat a sudden run of 409s as a signal that the session was dropped.
What exactly does the send route accept?
Two fields. A recipient_id, which is the numeric account id rather than the @ handle, and text. There is no attachment parameter, so files cannot ride along. If the upstream answers with no message id the call surfaces as a 502 and is not charged, so a failed delivery does not quietly consume credit.
How do you read an inbox before replying?
Two routes cover it. Listing returns a count and an array of conversations, each carrying a conversation id, a type, and the participant ids. Fetching one conversation requires that conversation id and returns messages with four fields each: id, a millisecond epoch time, sender_id, and text. Omit the conversation id and the response is a 400 asking you to provide it.
What can this surface not do?
Six things, stated plainly. It cannot page back through history, because neither read route accepts or returns a cursor. It cannot reach a conversation the account is not in. It cannot send from a different account than the registered one. It cannot attach a file. It offers no read receipt or typing indicator. And it has no webhook, so new messages are found by polling the list route.
The three direct-message routes
| Route | What it needs | What it returns |
|---|---|---|
| List conversations | A registered session, no parameters | A count and an array of conversations |
| Fetch a conversation | A conversation id | Messages with id, time, sender_id, text |
| Send | A numeric recipient_id and text | Confirmation, or a 502 that is not billed |
| Every route | Billed at $0.0016 per call | 409 session_required when unregistered |
The Direct Messages lookup endpoints let you retrieve DM events for the authenticated user.
Questions and answers
- Can you send a DM to an @ handle?
- No. The send route takes recipient_id, the numeric account id, not the handle. Resolve the handle to its id first with a user lookup, then pass that id. Sending a handle where an id is expected is the most frequent cause of a rejected send.
- How many messages can you send an hour?
- 150 per key by default, enforced as a sliding window rather than a counter that resets on the clock. Exceeding it returns 429 with a Retry-After header. Attempts count toward the cap rather than successes, so a burst of failures still consumes budget.
- Why do all my message calls suddenly return 409?
- The session was deregistered. That happens when a call reaches for a conversation the registered account does not belong to: the call answers 401 and drops the session, and everything afterwards answers 409 with session_required until you register again.
- Can you attach an image to a message?
- No. The send route accepts only recipient_id and text, with no attachment parameter, so files cannot be delivered this way. That is a hard boundary of the surface rather than a configuration you can switch on.
- Is there a webhook for new messages?
- No. Nothing pushes an event when a message arrives, so new activity is discovered by polling the list route on whatever interval suits you. Since neither read route accepts a cursor, history beyond what a fetch returns is not reachable either.
- What does each message call cost?
- All three routes bill at $0.0016, the premium band, because a message response carries more than a standard public read. A send that fails upstream and surfaces as a 502 is not billed, so a failed delivery does not consume credit.
Keep reading
Start with $0.50 in free credits
No credit card. Roughly 12,500 tweets to test every endpoint.