The CodePen API v2 lets you read and write your CodePen Pens, Collections, and Assets from your own tools: scripts, build steps, CI jobs, editors, and AI agents.
For Pens, that covers their files, saved versions, builds, deployments, custom domains, preview URLs, screenshots, etc. A Pen made through the API builds and deploys exactly like one made in the editor.
The API has two surfaces, and they are equivalent. Pick whichever fits your tooling:
- REST API v2: plain JSON over HTTP
- GraphQL API v2: a single endpoint
Both run the same logic, so a change made through one is visible through the other. Keys, scope, limits, and error codes are shared, and this page describes them. The v1 API stays available, but v2 does everything v1 does and more, including reading Classic Pens, so use v2 for anything new.
Getting an API Key
- Go to Settings → API
- Click Generate API Key.
- Copy the key. Your own key starts with
cp_user_; a Team’s key starts withcp_team_.
Treat your key like a password: anyone who has it can read your Pens, Collections and Assets, including private ones, and change them. If a key leaks, click Rotate API Key in Settings. That issues a new key and the old one stops working immediately.
You can have one key at a time. Settings shows only its first and last few characters; click Reveal to see the whole key again whenever you need it.
The API is available to every account. Free accounts can use it with daily allowances; PRO accounts get higher limits and the PRO features (private Pens, Asset Hosting, and so on) they already have on the site. Teams must be PRO.
Making a Request
Every request goes to https://api.codepen.io/ and carries your key in the Authorization header. If a Bearer header is awkward for your tooling, an X-API-Key: cp_user_... header works too.
A REST request:
curl https://api.codepen.io/v2/pens \
-H "Authorization: Bearer cp_user_your_key_here"Code language: Bash (bash)
The same list through GraphQL:
curl https://api.codepen.io/v2/graphql \
-H "Authorization: Bearer cp_user_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "query": "{ pens { totalCount nodes { id title } } }" }'Code language: Bash (bash)
Responses are JSON. Requests without a valid key get a 401.
REST or GraphQL?
They do the same things, so it’s a matter of taste and tooling.
- REST is the simplest to call from a shell script, a CI job, or any HTTP client. Each endpoint returns a fixed JSON shape with snake_case keys. Lists are paged with
pageandper_pageand aLinkheader, like GitHub’s API. - GraphQL lets you ask for exactly the fields you want in one request: a Pen, its saved version, its build, its files, and its deployment together. It works with any GraphQL client. Lists are cursor-paginated. There is a GraphQL Playground here.
What You Can Access
A key belongs to a User or to a Team, and it works only with that owner’s work:
- Your personal key (
cp_user_) reads and writes your own Pens, Collections and Assets. It does not reach your Teams’ work, even Teams you belong to. - A Team’s key (
cp_team_) reads and writes that Team’s Pens, Collections and Assets. A Team has one key; any of the Team’s admins and owners can see or rotate it. Writes made with it are attributed to the Team’s owner.
Privacy doesn’t change this. Your own Pens are reachable whether they are Public, Private, Protected, or Collaborators-only. The API is for your own work only: other people’s Pens and Collections aren’t available through it, even public ones, and being a collaborator on someone’s Pen doesn’t change that. Requesting one comes back as 404, exactly like a Pen that doesn’t exist.
When you list a Collection you own, you get the Pens in it that are yours. Pens by other people are skipped.
Classic Pens and 2.0 Pens
- A 2.0 Pen is one made in the CodePen 2.0 editor. It has files, saved versions, builds and deployments, and the API can read and write all of them.
- A Classic Pen is one made in the classic editor, with HTML, CSS and JS panels. The API reads Classic Pens as a set of files (
/index.html,/style.scss,/script.jsand so on, named for the preprocessor each panel uses) but cannot change them. A Classic Pen has no versions and no deployment. Classic Pens can still be added to and removed from Collections.
Every Pen says which it is: editor is V2 or Classic.
Identifiers
IDs are the ones you see in CodePen URLs.
| Thing | ID |
|---|---|
| Pen | Its cpid (2.0 Pens) or its hashid, the slug in a Classic Pen’s URL. Either works for any Pen. |
| Pen file | A stable ID assigned when the file is created. It survives renames and edits. |
| Pen version | Its cpid. |
| Build | The ID of the version it built. A version and its build share one ID. |
| Collection | Its hashid, the ID in codepen.io/collection/<id>. |
| Asset | Its hashid. |
Privacy and Passwords
Pens and Collections have one privacy setting, access:
Public: listed on the site.Private: not listed on the site; anyone with the link can view it.Protected(Pens only): private, and viewing it needs a password.Collaborators(Pens only): private, and only collaborators can view it.
Anything but Public needs a PRO plan; a Free account gets a FORBIDDEN error rather than a silent fallback to Public. A Protected Pen needs a password of at least 8 characters when you create it, fork it, or switch it to Protected. The password is write-only: no read ever returns it.
Files, Versions and Deployments
A 2.0 Pen’s files live in saved versions. The API reads and writes the saved head, the Pen’s latest saved version. Every file write creates a new saved version and builds it, exactly as saving in the editor does (see Writes and Builds below). You can write one file, or several files in one version: create, overwrite, move, and delete in one go.
To keep an agent and a person in the editor from silently overwriting each other, a write can carry the ID of the version it was based on (expected_version_id in REST, expectedVersionID in GraphQL). If the Pen’s head has moved since, the write is refused with a CONFLICT error that names both versions, so you can read again and retry.
If someone has the Pen open in the editor when the API saves a new version, their tab is told right away. It goes read-only and asks them to refresh, so their next save can’t silently overwrite the API’s version. The API’s write always goes through. Any unsaved changes in that tab need to be copied out before refreshing.
Text files go in the JSON body. Binary files (images, fonts, audio) go up as parts of a multipart request, up to 30 MB per file and 120 MB per request. Your plan’s limits apply on save. Older versions can be listed, read and restored to the head. A version, usually the head, can be deployed to the Pen’s URL and taken down again.
Deleting and Restoring
Deleting a Pen or a Collection through the API works the same as deleting it on the site: it goes to the trash, not away for good. Deleted items are listed at Your Work → Deleted Items, and you can restore them from there for 3 days. After that they’re gone permanently. So if a script or agent deletes something by mistake, you have time to get it back.
Deleting a Collection trashes only the Collection itself; the Pens in it are untouched.
Deleting doesn’t give back a daily create. The allowance counts how many Pens and Collections you created today, not how many you have. See Limits.
Writes and Builds
Pens are compiled. The files you write are source files, and the CodePen compiler turns them into the files that actually get served: TypeScript into JavaScript, Sass into CSS, and so on. So writing to a Pen is less like saving a file to a disk and more like pushing a commit to a repository with CI attached. The write is accepted, and then a build runs.
Every write to a Pen’s files (creating a Pen, forking one, and writing, moving, or deleting files) always makes a new saved version and always starts a build of it. Sometimes more than one build, because the first build can come back asking for changes.
The Compiler Can Change Your Files
Sometimes the compiler needs the source files to change before it can build them. Those changes are called patches. A few examples:
- You add a
.tsfile to a Pen for the first time. A patch adds/tsconfig.json. - Your
index.htmllinks to a JavaScript file that uses ES Modules, but the<script>tag is missingtype="module". A patch fixes the tag in the file you just wrote. - Your code imports a package. A patch adds the dependency to
package.json. - The Pen’s config file needs to match the kinds of files now in the Pen. A patch updates it.
- The Pen formats on save. A patch carries the formatted code.
In the editor, patches are applied as you work. Through the API, they apply patches for you: the API applies the patches, rebuilds, and saves the result as one more version, however many builds that takes (at most five). So a write produces one version, or two when the compiler asked for changes:
- A version holding exactly the files you sent.
- A version holding those files with the compiler’s changes applied. This one becomes the Pen’s head, and its build lists the patches that were applied.
The important part: after a write, the Pen’s files may not be the files you sent. New files may appear, and a patch may change a file you just wrote.
Wait for the Build Before Writing Again
If your tool makes more than one write to a Pen, treat each write as a round trip:
- Write. The response carries the newest version the write produced and that version’s build.
- Wait for the build to finish. A write waits for its build before answering, 45 seconds by default. You can change that with
wait(up to 75 seconds, or0to answer as soon as your files are saved). If the build is still going when the response comes back, poll the build until it is done. - Read the files again. Use the files of the newest version, not your own copy of what you sent.
- Base the next write on that version. Pass its ID as
expected_version_id(expectedVersionIDin GraphQL), so a write based on stale files is refused rather than saved over newer ones.
Writing again while builds are still running means writing on top of files that are about to change. At best, your next write is refused with a CONFLICT. At worst it undoes a fix the compiler made, and the next build has to make it again.
Build Status
| Status | Meaning |
|---|---|
Queued, Running | The build isn’t done. Keep polling. |
Success | The build finished and has output files. |
Failed | The build finished with errors. Your files are saved anyway, and the build’s diagnostics say what went wrong. Nothing is retried; fix the files and write again. |
Canceled | The compiler asked for changes. If the build points to a next build, the API is applying them: follow it. If it doesn’t, the changes could not be applied (or five builds weren’t enough), and the build lists the patches still required so you can apply them yourself and write again. |
A write is settled when its newest build is Success or Failed, or Canceled with no next build.
Source Files and Output Files
There are two sets of files, and they are different things:
- A version’s
filesare the source files (thinksrc): what you wrote, plus any changes the compiler made. These are the files you read, edit, and write back. - A build’s
filesare the compiled output (thinkdist): what the Pen’s preview and deployment serve. They exist only when the build succeeded. Read them if you need the built result; you never write them.
Restoring an older version is the one write that doesn’t build: the restored version keeps the build of the version it was copied from.
The REST and GraphQL pages each have a Builds section with the fields, how to poll, and examples.
Limits
| Plan | Requests | Reads | Creates per day |
|---|---|---|---|
| Free | 100/hour | 1,000/day | 3 Pens (creates and forks together) and 3 Collections |
| PRO (User or Team) | 3,000/hour | 10,000/day | 50 Pens and 50 Collections |
Past a limit, you’ll get 429 responses until the hour or day is up. Each day runs from the first request of that kind, and rotating your key does not reset the counters. A Team key counts against the Team, separately from its members’ own keys.
The daily create allowance counts creates, not the Pens or Collections you currently have, so deleting one doesn’t give it back. A Free account that creates 3 Pens and deletes one still can’t create another that day.
A few more:
- Pages hold at most 100 items.
- A GraphQL request carries one operation (no batching) and has a complexity limit; see the GraphQL page.
- A JSON body is capped at 16 MB; a multipart request at 120 MB and one file at 30 MB.
- A write waits at most 75 seconds for its build. The build carries on after that; poll it.
- Asset filenames must be unique among your Assets and cannot contain
/or\.
Errors
Both surfaces use the same error codes. REST puts the code in the body with an HTTP status; GraphQL puts it in errors[].extensions.code.
| Code | HTTP status | When |
|---|---|---|
UNAUTHENTICATED | 401 | The key is missing or not valid, or the Team it belongs to isn’t PRO. Check the header, or rotate the key. |
RATE_LIMITED | 429 | Over the hourly request limit, the daily read limit, or today’s allowance of Pens or Collections. |
NOT_FOUND | 404 | No Pen, Collection, version, build, file, Asset or deployment with that ID, or one that isn’t yours. |
FORBIDDEN | 403 | Writing to a Classic Pen; a plan limit (file count, file size, storage, non-Public Pens or Collections, collaborator invites). |
BAD_USER_INPUT | 400 | An invalid path, setting, access level or role; empty content; both content and an upload for one file. |
CONFLICT | 409 | The version you based a write on is no longer the Pen’s head. |
INTERNAL | 500 | Something unexpected on our end. We’re notified. |
When a plan limit is the reason for a FORBIDDEN or RATE_LIMITED error, the message says what the limit is and, if a bigger plan would lift it, links to pricing. For example:
daily limit reached: the Free plan allows 3 Pens a day through the API. Upgrade to PRO for 50 a day at https://codepen.io/pricing
A build that fails is not an error response. The write succeeded and your files are saved; the failure is reported in the build’s status and diagnostics.
Next Steps
- REST API v2: every endpoint, the JSON objects, and
curlexamples. - GraphQL API v2: the schema, every query and mutation, and example operations.
Questions
Contact support and mention the API. We’re interested in evolving it to meet your needs.