How do you export your Twitter bookmarks?
Last updated August 24, 2026
Register your x.com session once, then page user/bookmarks with cursor until next_cursor comes back null and you have the whole saved list. user/bookmark_search filters it by query, user/bookmark_folders names your collections, and user/bookmark_folder_timeline reads one of them. Every one of those reads is a flat $0.0008, and registering the session itself is free.
Every rate here is the pricing TwitterAPIs publishes. The billed rate is $0.0008 per call; $0.04 per 1,000 tweets is derived from it at a full 20-tweet page, which is the default page size rather than a guaranteed yield (source: twitterapis.com/pricing).
Why bookmarks need your own session
A bookmark is private to the account that saved it, so this family cannot work the way a public tweet read does. Most routes on this API run against a pool of accounts, which is fine because the data is public and the answer is the same whoever asks for it. Your saved list is not public and the answer is specific to you, so every route here reads from a session you supply. That is one extra step at setup and it changes nothing about the requests afterwards: you still send the same Authorization header carrying Bearer and your key, and the stored session is applied automatically to any route that needs to act as you.
Registering auth_token and ct0
customer/session takes auth_token and ct0, the two cookie values from a browser already logged in to x.com, in a JSON request body rather than the query string. Two optional fields sit beside them: user_agent, which defaults to a current Chrome string, and proxy_url, for routing session traffic through an HTTP or SOCKS proxy instead of going direct. A missing or empty auth_token or ct0 is a 400. The reply carries ok, a readable message, and username, the handle resolved from the session, which comes back null when the identity lookup did not resolve. Registration is charged nothing at all. If you would rather store nothing, any single call can carry the same pair as x-auth-token and x-ct0 headers.
Stored is not the same as working
ok true means the pair was stored, not that it works. An expired or revoked pair is accepted and still returns 200, because this call deliberately does not gate on its own probe. The field that answers the real question is validation, and it has three values. validated means X answered an authenticated request with 200. dead means X explicitly rejected the cookies with a 401, a 403 or an auth error body, and it is the only state that means they are definitely no good. unknown means the probe itself did not resolve, through a timeout, a rate limit or a proxy fault, and treating unknown as invalid is precisely the mistake this three way split exists to prevent. validation_reason carries a machine stable slug beside it.
Paging the flat list
user/bookmarks returns your saved tweets newest first. count defaults to 20, and cursor carries your place in the list. The response holds three fields: count, the number of tweets on this page, next_cursor, and the tweets array itself. Loop while next_cursor is a non empty string and stop when it comes back empty or null, which is the end of list signal on this route. Because a request costs the same whatever the page holds, a larger page is straightforwardly cheaper per saved tweet, so raise count if you are exporting an archive rather than polling for whatever is new since yesterday. The tweets themselves are ordinary tweet objects, so an existing renderer needs no changes.
Searching inside what you saved
user/bookmark_search takes a required query and returns only the saved tweets that match it, with the same count default of 20 and the same cursor mechanism. The matching happens upstream, so this is genuinely a filter rather than a client side scan: you are not downloading the whole archive in order to grep it locally. On a long saved list that is the difference between one call and several hundred. The response shape is identical to the flat list, count plus next_cursor plus tweets, so a single parser and a single paging loop serve both routes and the only thing that changes between them is which path you call and whether you pass a query.
Folders resolve from the session alone
user/bookmark_folders takes no request parameters whatsoever. Your collections, which is X's own internal name for them, resolve from the session by themselves, so there is nothing to pass and nothing to look up first. It answers with count and a folders array, each entry holding id, name and cover_media_url. A null cover_media_url means that folder is empty rather than that an image failed to load or failed to parse, which is worth encoding in whatever renders the list, because an empty collection with a real id and a real name is a perfectly normal thing to have. count is simply how many folders you own, and there is no cursor on this route at all, so the entire list arrives in one call. Take an id from it and you can read what is inside.
Reading one folder
user/bookmark_folder_timeline reads the tweets saved inside a single folder, identified by a required folder_id taken from the folders call. It is cursor paginated and it has no count parameter at all, unlike every other read in this family, so page size is not yours to choose on this route. The response echoes folder_id back alongside count, next_cursor and tweets, which makes an interleaved multi folder export easy to reassemble without separately tracking which request produced which page. It can only read folders belonging to your own session. There is no parameter pointing it at somebody else's collection, because X exposes no way to ask that question in the first place.
Saving and removing a bookmark
Two write routes complete the set. tweet/bookmark takes the numeric id of a tweet and saves it to the acting account, answering with ok true. tweet/unbookmark takes the same argument and removes it, answering the same way. Neither hands the tweet object back, only the acknowledgement, so keep the id you sent if you need it afterwards. Both act as the account whose session you registered, which turns your saved list into a workable queue rather than an archive you only ever add to: a script can file posts as it encounters them and clear them once processed. Both bill at the ordinary rate rather than the higher tier tweet creation and direct messages sit in.
Revoking, and what a full export costs
customer/session/delete removes the stored pair, takes no parameters, and is free for a deliberate reason. Billing charges the standard read rate for any endpoint it does not recognise and refuses a call on a short balance, so pricing revocation at zero is what guarantees a customer out of credits can still delete their own credentials. It is idempotent and returns 200 either way, with deleted false when nothing was stored. It deletes the stored copy and does not log the account out of x.com, so revoke from X account settings too if you want the cookies themselves killed. Afterwards these routes answer 409 session_required. On cost, 2,000 saved tweets at a 20 tweet page is 100 calls, about eight cents. $0.0008 a call, per our published pricing.
The six bookmark routes
| Route | Method | What it needs |
|---|---|---|
| user/bookmarks | GET | Your session, with count and cursor optional |
| user/bookmark_search | GET | A required query string |
| user/bookmark_folders | GET | No parameters at all |
| user/bookmark_folder_timeline | GET | A folder_id, cursor only, no count |
| tweet/bookmark | POST | The tweet id to save |
| tweet/unbookmark | POST | The tweet id to remove |
When an API response contains more results than can be returned at once, use pagination to retrieve all pages of data.
Questions and answers
- Can I read another account's bookmarks?
- No. Every route here resolves from the session you registered, and the folder timeline is explicit that it reads only folders belonging to your own session. There is no user_id parameter pointing at somebody else. Bookmarks are private on X, so nothing in this family exposes them for an account you do not control, and no combination of parameters gets around that.
- How do I set up the session?
- Send the auth_token and ct0 cookie values from a logged in x.com browser to customer/session in a JSON body. It stores them against your API key, then probes them and reports the outcome as validated, dead or unknown. Optional user_agent and proxy_url fields let you control how that session traffic is sent. The call itself is free, and a missing or empty cookie value is a 400.
- What if my session stops working?
- Read validation rather than ok. Only dead means X actually rejected the cookies, which is your signal to log in again and register a fresh pair. unknown means the probe did not resolve, through a timeout, a rate limit or a proxy fault, and is not evidence of anything being wrong. A session marked dead is still stored and still revocable, so it does not have to be cleared before you replace it.
- Can I use these routes without storing my cookies?
- Yes. Any single call can carry the same pair per request as the x-auth-token and x-ct0 headers instead of registering them once. Credentials sent that way are never stored, which also means there is nothing to revoke afterwards and the delete route reports deleted false for a caller who only ever used that mode. It is the right choice for a short lived job.
- How many bookmarks come back in one call?
- The count parameter defaults to 20 on the flat list and on the search route, and the folder timeline has no count parameter at all, only cursor. Because each request is billed once whatever the page holds, a bigger page is straightforwardly cheaper per saved tweet. At the default page size, 1,000 exported bookmarks land around four cents. Raise count on the flat list and on the search route; the folder timeline gives you no such lever, so its page size is whatever X sends.
- Why is cover_media_url null on a folder?
- That folder is empty. An empty collection returns null in that field rather than a URL, and it is a real answer rather than a parse failure or a dropped image. The id and name beside it are still valid, so you can page the folder timeline for it exactly as you would any other folder and get back an empty tweets array.
- Can I add a bookmark through the API?
- Yes. tweet/bookmark takes the numeric tweet id and returns ok true once it is recorded, and tweet/unbookmark reverses it with the same argument. Both act as the account whose session you registered, so a script can file posts as it finds them and clear them again once you have processed the queue. Neither returns the tweet itself, only the acknowledgement.
- How do I export folders as well as the flat list?
- Call user/bookmark_folders first for the id and name of each collection, then loop user/bookmark_folder_timeline once per folder id, paging on cursor. The flat list from user/bookmarks is a separate view, so run that too if you want everything. Each request is charged on its own, folder by folder, and the response echoes folder_id so interleaved pages reassemble easily. Both routes read only what belongs to your own session, so the loop is bounded by how many folders you have rather than by anything you must discover first.
- Why does the folder timeline have no page size parameter?
- Because the upstream operation does not offer one. Every other read in this family accepts count and clamps it, but the folder route takes folder_id and cursor and nothing else, so page size is fixed and paging is the only lever you have. Plan a folder export around number of pages rather than around a page size you can tune.
- How do I delete the session when I am done?
- Call customer/session/delete. It takes no parameters, revokes only the session stored against the key on the request, and is free so that a customer out of credits can still remove their own credentials. It is idempotent, returning 200 with deleted false when nothing was stored. It removes the stored copy and does not sign the account out of x.com, so revoke in X settings too if you want the cookies dead.
Keep reading
Start with $0.50 in free credits
No credit card. Roughly 12,500 tweets to test every endpoint.