September 13, 202611 min

ChatGPT Share Link JSON: Get the Raw Conversation Data

Someone sends you a chatgpt.com/share/... link, or you created one yourself, and you want the conversation as structured data rather than as a rendered page. Copy-paste gives you a wall of text with the roles flattened, the code blocks mangled and the ordering silently wrong wherever the conversation branched.

The good news is that the structured version is already there. A shared conversation page is not assembled from prose; it is assembled from JSON that has the same broad shape as the one in your data export, and that JSON is reachable from the page you already have open. The less good news is that none of it is a documented interface, so the honest goal is not "memorise this path" but "learn to find it again after it moves".

This post covers both routes into the data, the shape you get back, how to reconstruct the thread you actually see on screen, and where the whole approach falls over.

Why the Page Already Contains the Data

A shared conversation is server-rendered. For the page to show you the conversation on first paint, the conversation has to be in what the server sent, which means the data is embedded in the HTML document or fetched immediately alongside it. Either way it is on your machine before you do anything clever.

That single fact is what makes this tractable. You are not reverse-engineering a private API or trying to get at something you were not given. You were handed the conversation. The only question is whether you read the copy that was rendered for your eyes or the copy that was rendered for the page's JavaScript.

The second copy is better in every way that matters. It keeps the author role on each turn, keeps message boundaries intact, keeps code blocks as content rather than as styling, and preserves the branch structure that a visual read cannot show you at all.

Two Routes In, and They Are Not Equivalent

Route one: read the JSON that is already in the page. View source, or open DevTools and look at the document response. The conversation payload is embedded in the markup as JSON inside a script element. This route works whenever the page itself works, because you are reading exactly what your browser was given.

Route two: call the endpoint the page calls. Open DevTools, switch to the Network tab, reload the share link, and look at the requests with a JSON content type. At the time of writing the relevant one sits under a /backend-api/ path keyed by the share id from the URL. Copy the request as cURL from DevTools and you have a working call.

Route one is more robust and route two is more convenient. Neither is a supported interface, and the second is the one that breaks first, because a path under /backend-api/ is internal plumbing that nobody promised you.

Note what both routes have in common: you start from the page in a browser that is already loading it successfully. That ordering is not an accident, and the next section explains why.

Do Not Start With curl

The most common way this goes wrong is reaching for curl first. You paste the share URL into a terminal, get back an HTML challenge page or a 403, and conclude that the link is broken or that you need an API key.

Neither is true. Consumer web properties sit behind bot protection that evaluates far more than the URL: header ordering, TLS fingerprint, whether the request looks like it came from a browser session at all. A bare curl https://chatgpt.com/share/... looks like exactly what it is, and gets treated accordingly.

The practical consequences:

  • Start in the browser. Open the link, confirm it renders, then work from the page or from DevTools.
  • Copy as cURL rather than composing by hand. DevTools reproduces the full header set from a request that already succeeded, which is a far better starting point than guessing at a user agent string.
  • Treat a failure as a signal about the request, not the data. If the browser shows the conversation and your script does not, the difference is in how you asked.
  • Expect fragility. Anything you build on top of this can stop working without warning or notice, because there was never an interface contract to break.

If you need a copy you can rely on tomorrow, the answer is not a more convincing script. It is to save the artefact while you have it, which the last section covers.

The Shape You Get Back

Once you have the JSON, the structure will be familiar if you have ever opened a data export. The important fields:

  • title — the conversation title.
  • create_time and update_time — Unix epoch seconds, with a fractional part. These are the same timestamps, with the same traps, as the ones in the bulk export. Seconds rather than milliseconds, frequently null at the message level, and covered in detail in ChatGPT Export Timestamps: create_time and update_time.
  • mapping — an object keyed by node UUID. This is the conversation.
  • current_node — the UUID of the last node on the branch that is actually displayed.

Each entry in mapping is a node with an id, a parent pointing at another node id or null, a children array, and an optional message. When a message is present it carries an author role, the content parts, its own timestamp and a metadata object.

The critical thing to internalise: mapping is a tree, not a list. It is a tree because ChatGPT supports regenerating a response and editing an earlier message, and both operations create a sibling branch rather than overwriting what was there. A conversation where you regenerated an answer three times has all four answers in mapping. The page shows you one of them.

This is the same structure as the bulk export, so the file-level walkthrough in How to Read and Use Your ChatGPT conversations.json applies directly to what you are holding. What you have here is one conversation rather than an array of them.

Walking the Tree to the Thread You Can See

Iterating over mapping and printing every message is the mistake everyone makes once. You get every abandoned draft, every regenerated alternative and every edited-away version, in whatever order the object happened to serialise, and none of it matches the page.

The visible thread is the path from current_node back to the root. The algorithm is four lines of any language:

  1. Start at the node id in current_node.
  2. If the node has a message, keep it.
  3. Move to the node named by parent.
  4. Stop when parent is null, then reverse what you collected.

That gives you the displayed conversation in order. Two details save you an afternoon. First, not every node on the path has a message: the root is structural, and there are system nodes that were never a visible turn, so skip a node with no message rather than crashing on it. Second, filter on the author role. A path contains system and tool nodes alongside the user and assistant turns, and rendering those unfiltered produces a transcript full of material no human ever saw on the page.

If you want the branches rather than the thread, invert the walk: start at the root and follow children, treating any node with more than one child as a branch point. That is how you recover the answers a regeneration replaced, which is occasionally the entire reason someone wants the JSON.

About That Word "Pagination"

A cluster of searches pairs the share endpoint with pagination, and it deserves a direct answer rather than an invented one.

There is no published pagination contract here, and you should not assume the shape of one. What you can do is check empirically, which takes about a minute:

  • Count the nodes in mapping that carry a message with a user or assistant role.
  • Count the turns rendered on the page.
  • If the numbers agree, you have the whole conversation and pagination is not your problem.
  • If the JSON has fewer turns than the page, watch the Network tab while you scroll the page and see whether a second request fires.

That procedure gives you a true answer about the conversation in front of you today, which is worth considerably more than a parameter name copied from an article that may have been written against a version that no longer exists. Very long conversations are the ones worth checking, because they are where any truncation would show up first.

The same empirical habit applies to every field described in this post. Read the response you actually got. Nothing here is a specification.

Use It on Links You Are Entitled To

Worth stating plainly, because "there is an endpoint" invites bad instincts.

A share link is a deliberate act of publication by whoever created it, and pulling the structured version of a page you were given is a reasonable thing to do with your own conversations and with links shared with you. Enumerating share ids, harvesting other people's conversations in bulk, or pointing automated traffic at a consumer web property is a different activity with different consequences, and OpenAI's terms of use govern it rather than this article.

There is also a privacy point that has nothing to do with terms. People share conversations without rereading them, and a shared chat routinely contains more than its author intended: a pasted file, an internal name, a fragment of a system prompt. If you archive other people's share links at any volume you are building a collection of other people's incidental disclosures. Archive your own by default.

Where This Approach Genuinely Falls Over

Three limits, so you can decide early whether to invest in tooling around it.

Share links are snapshots. A shared conversation reflects the state at the moment it was shared. Carry on the chat afterwards and the share link does not follow. Whatever you pull is a point-in-time copy and will not update.

They can be revoked. The creator can delete a share link. When that happens the data is gone for you, and any pipeline that resolves links on demand rather than at capture time now has holes in it.

None of it is a contract. The embedded payload, the endpoint path, the field names: all of it is implementation detail that has changed before and will change again. Code written against it needs a maintainer, and the failure mode is usually silent, which is the worst kind.

Taken together, these say something specific. The share-link JSON is an excellent way to get a conversation out right now. It is a poor foundation for a system you expect to keep running unattended.

Keeping the Conversation Instead of Re-Fetching It

Which leads to the practical conclusion. If a conversation matters, capture it while you can see it rather than storing a link and hoping.

The plainest version is a file. With the shared page open, press Ctrl or Cmd plus S and save it. You get a local copy of the rendered conversation that does not depend on the link surviving, on your script still matching an undocumented shape, or on a bot check letting you through. For anything bulkier, your own account's data export is the archive of record, and what it contains and how to work through it is in What to Do With Your ChatGPT Data Export. The wider argument for keeping copies at all is in How to Back Up Your AI Conversations.

Those saved pages are also the input MindLock takes. You upload the HTML file you saved, distillation turns the conversation into compact memory documents, and a generated context block is what you paste into your next chat so you are not re-explaining a project from scratch. Two honest caveats, since both are easy to assume otherwise. Nothing is captured automatically: the save is a keystroke you press, or a one-click button if you have sideloaded the Chrome extension, and MindLock does not import conversations.json at all. And memory documents change when you re-run distillation rather than continuously, so a document is exactly as current as your last distil. The import path itself is walked through in Importing Conversations.

The Short Version

A shared ChatGPT conversation is backed by JSON that is already on your machine by the time the page renders. Read it out of the page source, or copy the request the page makes from the DevTools Network tab, and start from a browser session that is already working rather than from a bare curl that bot protection will reject.

What you get is a mapping tree keyed by node UUID plus a current_node pointer. Walk parent links back from current_node and reverse to get the thread you can see; follow children from the root instead if you want the branches a regeneration hid. Skip nodes without a message and filter by author role.

Verify pagination rather than assuming it, by comparing node count against rendered turns. Treat every field name here as observation rather than specification, since none of it is documented and all of it has moved before. And if a conversation is worth having, save the page while it is in front of you, because a link you cannot re-fetch is not an archive.