Skip to main content

Portfolio

Give every user a polished, always-current portfolio. You curate a board, add their work, and either publish it as a hosted public gallery or fetch the assets and render your own UI. Update the board and the portfolio updates itself — no redeploy.

A portfolio site built on the Playbook API

The flow

  1. Create a board — the portfolio container.
  2. Add work to it (from URLs, or a direct upload).
  3. Publish the board for a hosted public gallery + embeddable iframe, or fetch the board's assets and render your own gallery.

1. Create the portfolio board

curl -X POST "https://api.playbook.com/v1/$ORG/boards" \
-H "Authorization: Bearer $PLAYBOOK_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "title": "Jordan Lee — Photography", "description": "Selected work, 2026" }'

The response includes the new board's token (e.g. jordan-lee-photography). See Creating Boards.

2. Add work to the board

Point Playbook at the image URLs — it fetches, processes, and generates delivery-ready renditions for you:

curl -X POST "https://api.playbook.com/v1/$ORG/assets/batch_create_from_urls" \
-H "Authorization: Bearer $PLAYBOOK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"batch": {
"collection_token": "jordan-lee-photography",
"assets": [
{ "uuid": "1", "uri": "https://example.com/shoot/01.jpg", "title": "Coastline" },
{ "uuid": "2", "uri": "https://example.com/shoot/02.jpg", "title": "Studio portrait" }
]
}
}'

Uploads are asynchronous — each asset comes back as a skeleton and finishes processing in the background. See Uploading Assets for the signed-upload flow (for local files) and how to poll for completion.

The fastest path: publish the board and hand back a public URL.

curl -X POST "https://api.playbook.com/v1/$ORG/boards/jordan-lee-photography/publish" \
-H "Authorization: Bearer $PLAYBOOK_TOKEN"
{ "data": { "url": "https://playbook.com/p/your-org/jordan-lee-photography" } }

Embed it anywhere:

<iframe
src="https://playbook.com/p/your-org/jordan-lee-photography"
style="width:100%;height:800px;border:0"
title="Portfolio"
></iframe>

See Sharing & Publishing for the difference between shared and published links.

Prefer full control over the markup? Fetch the board's assets and lay them out yourself. Each asset carries a CDN display_url.

Portfolio.tsx
const ORG = "your-org";
const BOARD = "jordan-lee-photography";
const TOKEN = process.env.PLAYBOOK_TOKEN;

type Asset = { token: string; title: string; display_url: string };

async function getPortfolio(): Promise<Asset[]> {
const res = await fetch(
`https://api.playbook.com/v1/${ORG}/boards/${BOARD}/assets?per_page=50`,
{ headers: { Authorization: `Bearer ${TOKEN}` } },
);
const { data } = await res.json();
return data;
}

export default async function Portfolio() {
const assets = await getPortfolio();
return (
<div style={{ columns: 3, columnGap: 12 }}>
{assets.map((a) => (
<img
key={a.token}
src={a.display_url}
alt={a.title}
loading="lazy"
style={{ width: "100%", marginBottom: 12, borderRadius: 8 }}
/>
))}
</div>
);
}

To keep the portfolio fresh, just add or reorder assets on the board — both the published gallery and your custom render reflect it on the next load.