Your first request, and the ids everything else needs.

Getting started

The NodePen API lets a script read what a team holds. Everything lives under /api/v1 on https://app.nodepen.io. Anything outside that prefix is the web app talking to itself — those endpoints change with the UI and will refuse an API token.

The whole surface is read-only. Every endpoint is a GET, and no scope changes anything.

The shape of a document in particular is the app's own internal type and will change. Nothing here is deleted or renamed without warning, but do not assume the stability you would expect of a frozen API yet. Tell us what you are building and it will shape what stabilises first.

Your first request

Create a token in Team settings → API tokens, then start here. Everything else needs an id, and this is where you get the first one. The token already says which team, so there is nothing to pass.

curl -H "Authorization: Bearer $NODEPEN_TOKEN" \
  https://app.nodepen.io/api/v1/team
{ "team": { "name": "My Team", "rootFolderId": "O7VSE6B0KZE2" } }

A folder, and what is in it

curl -H "Authorization: Bearer $NODEPEN_TOKEN" \
  https://app.nodepen.io/api/v1/folders/O7VSE6B0KZE2

root works in place of an id and gives you the same folder /api/v1/team points at, so a script that only wants to walk the tree can start here and skip the call above.

curl -H "Authorization: Bearer $NODEPEN_TOKEN" \
  https://app.nodepen.io/api/v1/folders/root
{
  "folder": {
    "id": "O7VSE6B0KZE2",
    "name": "Root Folder",
    "parentId": null,
    "path": [],
    "folders": [{ "id": "K2P0LMN4QRST", "name": "Studies" }],
    "documents": [
      {
        "id": "PH1J4SA5JBEK",
        "name": "Bridge study",
        "visibility": "private",
        "createdAt": "2026-09-04T09:21:11.972Z",
        "updatedAt": "2026-09-04T09:21:12.402Z"
      }
    ]
  }
}

path is the ancestors of this folder, root first, and is empty at the root. folders and documents are only what is directly inside — walk folders to go deeper.

Finding a document

Walking folders only finds a file if you already know which folder it is in. This is the other way round — the same search the search bar on the team page runs, over everything the team keeps:

curl -H "Authorization: Bearer $NODEPEN_TOKEN" \
  "https://app.nodepen.io/api/v1/documents?q=bridge"

q matches anywhere in a name and ignores case, so bridge finds Bridge study and Cable-stayed bridge alike. Names the query starts come first, and the rest are ordered by when they were last changed. % and _ are matched as themselves rather than as wildcards, so searching for 50% finds 50% scale.

Leave q off and you get the team's documents, newest first — a bare /api/v1/documents is the "what has this team got" call rather than an error.

folderId narrows it to one folder and everything inside it, at any depth. root works in place of an id here just as it does above. A folder that is not there and a folder that is not yours are both 404, in the same words.

folder is where each document is kept: hand its id to /api/v1/folders/{folderId} to see what else is in there. isRoot marks the folder a file sits in when nobody has filed it anywhere — that folder's name is an internal placeholder, so show your team's own name against it rather than what is in name.

At most 50 documents come back and there is no paging yet, so narrow with q or folderId rather than expecting to walk the whole team.

Reading a document

curl -H "Authorization: Bearer $NODEPEN_TOKEN" \
  https://app.nodepen.io/api/v1/documents/PH1J4SA5JBEK

The nested document is the script itself, and it is the same live state the editor is showing — edits made a second ago are in it. document.version is the number to branch on if this shape changes. It is 1 and has never been anything else.

Within a node, inputs and outputs map port ids to their order; sources is the wiring, mapping one of your input ports to the upstream { nodeInstanceId, portInstanceId } feeding it; and values holds anything typed in by hand rather than computed.

Reading a solution

A solution is what the solver worked out for a document: a tree of values per node, and optionally the geometry that goes with them.

curl -H "Authorization: Bearer $NODEPEN_TOKEN" \
  https://app.nodepen.io/api/v1/documents/PH1J4SA5JBEK/solution

data is the solve as the solver produced it, keyed by node instance id — the same values the editor draws. data.solutionId names this solve, so you can tell one from the next.

Geometry is off by default, because reading and parsing the model file is by far the most expensive part of this endpoint and most callers only want the values:

curl -H "Authorization: Bearer $NODEPEN_TOKEN" \
  "https://app.nodepen.io/api/v1/documents/PH1J4SA5JBEK/solution?includeGeometry=true"

Asking for geometry is a request, not a guarantee. A solve can have no model file, and reading one can fail — both still answer 200 with the values, because those are the real result. includesGeometry is what tells you which you got, so check it rather than assuming.

?format=bimz answers with the same solve as a BIM object document instead: a flat store of objects keyed by platform id, one per value in a data tree branch, each carrying its geometry and pointing at the node, port and branch it was computed for. It always attempts geometry, so includeGeometry does not apply to it.

Exporting a document

Grasshopper (.gh) and Rhino (.3dm), the same two formats the editor offers.

curl -H "Authorization: Bearer $NODEPEN_TOKEN" \
  "https://app.nodepen.io/api/v1/documents/PH1J4SA5JBEK/export?format=.3dm&rhinoVersion=8"

The response is a link, not the file. Follow it to download:

curl -o nodepen-export.3dm "$(curl -s -H "Authorization: Bearer $NODEPEN_TOKEN" \
  "https://app.nodepen.io/api/v1/documents/PH1J4SA5JBEK/export?format=.gh" | jq -r .export.url)"

format is required and takes the leading dot. rhinoVersion is 7, 8 or 9, defaults to 9, and is only accepted alongside format=.3dm — sending it with .gh is an error rather than being quietly ignored. The url stops working at expiresAt, an hour out.

Writing

Not yet, and not by accident. A NodePen document is a CRDT that several people and the agent may have open at once, so "replace this with that JSON" is last-writer-wins against whoever is editing right now — which is precisely what the editor's own save path goes out of its way not to be. Rather than ship a write endpoint that quietly loses work, there is none. If you need one, say what you are trying to do; it changes what the right shape is.