The CodePen API lets you pull the source of your Pens out of CodePen and into your own tools. Point it at a Collection and you can fetch every Pen in it, with the HTML, CSS and JavaScript exactly as you wrote them, ready to drop into a repo, a build, or a docs site.

The API is read-only and available to PRO members.

This is an early version of the API. It covers Classic Pens today; support for 2.0 Pens is coming.

Getting an API key

  1. Go to Settings → API on codepen.io.
  2. Click Generate API Key.
  3. Copy the key. It starts with cp_.

Treat your key like a password: anyone who has it can read whatever you can read on CodePen, including your private Pens. 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.

Making a Request

Every request goes to https://api.codepen.io/v1/ and carries your key in the Authorization header:

curl "https://api.codepen.io/v1/pens/abcdef" \
  -H "Authorization: Bearer cp_your_key_here"

If a Bearer header is awkward for your tooling, an X-API-Key: cp_... header works too.

Responses are JSON. Requests without a valid key get a 401.

What You Can Access

Your key acts as you. It can read:

  • any public Pen or Collection
  • any Pen or Collection you own, public or private
  • any Pen or Collection owned by a Team you belong to, public or private

Team access comes from Team membership. It does not matter which Team you last switched to on the site.

Anything you can’t read comes back as 404, exactly like a Pen that doesn’t exist.

Endpoints

Get One Pen

GET /v1/pens/:id

:id is the Pen’s ID from its URL. For https://codepen.io/yourname/pen/abcdef the ID is abcdef.

curl "https://api.codepen.io/v1/pens/abcdef" \
  -H "Authorization: Bearer cp_your_key_here"

Returns a single Pen.

List the Pens in a Collection

GET /v1/collections/:id/pens

:id is the Collection’s ID from its URL. For https://codepen.io/collection/XYZabc the ID is XYZabc.

curl "https://api.codepen.io/v1/collections/XYZabc/pens?limit=50" \
  -H "Authorization: Bearer cp_your_key_here"

Pens come back in the order they were added to the Collection, oldest first:

{
  "pens": [ { "id": "abcdef", "title": "..." }, ... ],
  "next_cursor": "eyJ2IjoiMTY3NzI2NjAifQ",
  "has_more": true
}Code language: JSON / JSON with Comments (json)

Large Collections are paged. Use these query parameters:

ParameterWhat it does
limitHow many Pens per page. Default 20, maximum 100.
cursorWhere to continue from. Pass the next_cursor of the last page.

Keep requesting with the returned next_cursor until has_more is false. A page can contain fewer Pens than limit if some items in the Collection aren’t readable with your key, so always go by has_more rather than counting Pens.

Here is the whole loop in JavaScript:

async function fetchCollection(collectionId, apiKey) {
  const pens = [];
  let cursor = null;

  do {
    const url = new URL(
      `https://api.codepen.io/v1/collections/${collectionId}/pens`
    );
    url.searchParams.set('limit', '100');
    if (cursor) url.searchParams.set('cursor', cursor);

    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${apiKey}` }
    });
    if (!response.ok) throw new Error(`CodePen API: ${response.status}`);

    const page = await response.json();
    pens.push(...page.pens);
    cursor = page.has_more ? page.next_cursor : null;
  } while (cursor);

  return pens;
}Code language: JavaScript (javascript)

The Pen Object

Both endpoints return Pens in this shape:

{
  "id": "abcdef",
  "title": "Draggable Card with Snap-back",
  "description": "A card that can be dragged and snaps back to origin.",
  "html": "<div class=\"card\">...</div>",
  "css": ".card { ... }",
  "js": "gsap.to('.card', { ... })",
  "css_external": ["https://fonts.googleapis.com/css?family=Inter"],
  "js_external": ["https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"],
  "tags": ["drag", "spring"],
  "added_to_collection_at": "2026-07-25T09:00:00Z"
}Code language: JSON / JSON with Comments (json)
FieldMeaning
idThe Pen’s ID, the same one in its URL.
titledescriptionAs set in the Pen’s settings.
htmlcssjsThe source from the three editor panels, exactly as authored. If a panel uses a preprocessor (Pug, SCSS, TypeScript, and so on) you get that source, not the compiled output.
css_externalExternal stylesheets from the Pen’s settings, in load order.
js_externalExternal scripts from the Pen’s settings, in load order.
tagsThe Pen’s tags.
added_to_collection_atWhen the Pen was added to the Collection you requested it through. null when you fetch a Pen directly.

Lists are always present. A Pen with no external resources has "css_external": [], never null.

Errors

Errors are JSON with a message and a matching status code:

{ "error": "not found" }Code language: JSON / JSON with Comments (json)
StatusMeaning
400A bad limit or cursor. Check the query parameters.
401The key is missing or not valid. Check the header, or rotate the key.
404No Pen or Collection with that ID, or one you don’t have access to.
429Too many requests. Wait a minute and try again.

Limits

  • 1000 requests per minute per key. Past that you’ll get 429 responses until the minute is up.
  • Only Classic Pens are returned for now. A 2.0 Pen answers 404 when fetched directly and is skipped in Collection listings.
  • The API is read-only. It can’t create, edit or delete anything.
Was this article helpful?
YesNo
Last updated: September 3, 2026