Sharing and Publishing
Learn how to share assets and boards with external stakeholders and publish boards to the web.
Overview
Playbook offers two main ways to make your content accessible to others:
- Shared Links: Private, trackable links for specific assets or boards
- Published Links: Public web pages showcasing your boards
Prerequisites
- Access Token: API token with sharing permissions
- Organization Slug: Your organization identifier
- Asset/Board Tokens: IDs of content you want to share
Sharing Assets
Create temporary shared links for individual assets.
Creating an Asset Shared Link
curl -X POST "https://api.playbook.com/v1/my-org/assets/product-photo/share" \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
{
"data": {
"url": "https://playbook.com/s/abc123/product-photo"
}
}
JavaScript Example
async function shareAsset(assetToken) {
const response = await fetch(
`https://api.playbook.com/v1/${ORG_SLUG}/assets/${assetToken}/share`,
{ method: "POST", headers: { Authorization: `Bearer ${ACCESS_TOKEN}` } },
);
const { data } = await response.json();
return data.url;
}
// Usage
const shareUrl = await shareAsset("product-photo");
console.log("Share link:", shareUrl);
Sharing Boards
Create shared links for entire boards, allowing external viewers to see all assets within.
Creating a Board Shared Link
curl -X POST "https://api.playbook.com/v1/my-org/boards/main-collection/share" \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
{
"data": {
"url": "https://playbook.com/s/def456/main-collection"
}
}
Board Sharing Features
Shared board links include:
- All assets in the board
- Nested subboards (if applicable)
- Asset metadata (title, description, tags)
- Comments (optionally)
- Download capabilities (based on permissions)
Link Controls
Board share and publish requests accept the same optional controls:
curl -X POST "https://api.playbook.com/v1/my-org/boards/main-collection/share" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"expiration_date": "2026-09-01T12:00:00Z",
"disable_downloads": true,
"password": "client-review"
}'
Omitting a field preserves its existing value. Send an empty string for expiration_date or password to clear that restriction; send disable_downloads: false to allow downloads again. Sharing plans without link customization are forced to the 30-day expiry and cannot add download/password restrictions. Published-page controls are not individually plan-gated; the plan limits publishing itself.
Accepting Uploads on a Shared Link
Board share requests also accept enable_uploads. It lets anyone holding the link add files to the board without signing in — the way to collect files you cannot fetch yourself: give the URL to a client or a teammate and ask them to drop the files on it, then poll the board for what arrives.
curl -X POST "https://api.playbook.com/v1/my-org/boards/main-collection/share" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "enable_uploads": true, "password": "drop-files", "expiration_date": "2026-09-01T12:00:00Z" }'
Response:
{
"data": {
"url": "https://playbook.com/s/def456/main-collection",
"uploads_enabled": true
}
}
Three things to know before you turn it on:
- There is no upload-only mode.
enable_uploadsis a setting on a share link, so the same URL still shows the board and its assets to whoever opens it. If the recipient should not see what is already there, share a fresh, empty board instead. - This endpoint returns the board's existing link. If the board was already shared for viewing, enabling uploads grants upload rights to everyone who was given that URL — it does not mint a separate one.
uploads_enabledon the response tells you the state you just left the link in. - It requires a plan with shared-link customization. Other plans get
403rather than a silently ignored field, so a link you were told was created is never quietly read-only.
Pair it with password and expiration_date whenever the link leaves a group you trust.
Publishing Boards
Publish boards as public web galleries accessible to anyone with the link.
Creating a Published Board
curl -X POST "https://api.playbook.com/v1/my-org/boards/portfolio/publish" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"disable_downloads": true}'
Response:
{
"data": {
"url": "https://playbook.com/p/my-org/portfolio"
}
}
Published vs. Shared
| Feature | Shared Link | Published Link |
|---|---|---|
| Visibility | Private (requires link) | Public (indexable) |
| Revocable | Yes | Yes (can unpublish) |
| Password Protection | Yes (via UI) | No |
| Analytics | Detailed | Basic |
| SEO | No | Yes |
| Best For | Client reviews, approvals | Portfolios, galleries |
Use Cases
1. Client Review Process
Share assets with clients for feedback:
async function sendForReview(assetTokens, clientEmail) {
// Create shared links for each asset
const sharedLinks = await Promise.all(
assetTokens.map((token) => shareAsset(token)),
);
// Send email with links
await emailService.send({
to: clientEmail,
subject: "Assets for Review",
body: `
Please review the following assets:
${sharedLinks.map((url, i) => `Asset ${i + 1}: ${url}`).join("\n")}
Reply with your feedback.
`,
});
return sharedLinks;
}
// Usage
await sendForReview(
["design-v1", "design-v2", "design-v3"],
"client@example.com",
);
2. Portfolio Website
Embed published boards in your website:
<!-- Embed published board -->
<iframe
src="https://playbook.com/p/my-org/portfolio"
width="100%"
height="800px"
frameborder="0"
allowfullscreen
>
</iframe>
3. Temporary Access for Vendors
Share boards with vendors for a limited time:
async function grantVendorAccess(boardToken, vendorEmail, expiryDays = 7) {
// Create shared link
const { url } = await shareBo ard(boardToken);
// Send with expiry notice
await emailService.send({
to: vendorEmail,
subject: 'Asset Access Granted',
body: `
You have access to our assets for the next ${expiryDays} days:
${url}
This link will be revoked after ${expiryDays} days.
`
});
// Schedule revocation (implement based on your system)
await scheduleRevocation(url, expiryDays);
return url;
}
4. Social Media Sharing
Share individual assets on social platforms:
async function shareToSocialMedia(assetToken, platform) {
const shareUrl = await shareAsset(assetToken);
const asset = await getAsset(assetToken);
const shareText = encodeURIComponent(`Check out ${asset.title}: ${shareUrl}`);
const socialUrls = {
twitter: `https://twitter.com/intent/tweet?text=${shareText}`,
facebook: `https://www.facebook.com/sharer/sharer.php?u=${shareUrl}`,
linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${shareUrl}`,
};
return socialUrls[platform];
}
Downloading Shared Assets
Shared links can include download capabilities. Get download URLs for shared assets:
For Shared Assets
curl "https://api.playbook.com/v1/shared/my-org/assets/product-photo/download?sharedlinkslug=abc123" \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
{
"data": {
"display_url": "https://cdn.playbook.com/product-photo.jpg",
"raw_url": "https://cdn.playbook.com/product-photo-original.jpg",
"size": 2456789
}
}
JavaScript Example
async function downloadSharedAsset(sharedOrgSlug, assetToken, sharedLinkSlug) {
const response = await fetch(
`https://api.playbook.com/v1/shared/${sharedOrgSlug}/assets/${assetToken}/download`,
{
headers: {
sharedlinkslug: sharedLinkSlug,
},
},
);
const { data } = await response.json();
return data.raw_url;
}
Managing Shared Links
Tracking Shared Link Usage
Implement your own tracking system:
class SharedLinkTracker {
constructor() {
this.links = new Map();
}
async createTrackedLink(assetOrBoardToken, type = "asset") {
// Create the share link
const url =
type === "asset"
? await shareAsset(assetOrBoardToken)
: await shareBoard(assetOrBoardToken);
// Store with metadata
this.links.set(url, {
token: assetOrBoardToken,
type,
createdAt: new Date(),
views: 0,
lastAccessed: null,
});
return url;
}
trackAccess(url) {
const link = this.links.get(url);
if (link) {
link.views++;
link.lastAccessed = new Date();
}
}
getAnalytics(url) {
return this.links.get(url);
}
listActiveLinks() {
const now = Date.now();
const dayAgo = now - 24 * 60 * 60 * 1000;
return Array.from(this.links.entries())
.filter(([_, link]) => link.lastAccessed > dayAgo)
.map(([url, link]) => ({ url, ...link }));
}
}
Revoking Access
While there's no direct API to revoke shared links, you can:
- Delete the shared asset/board
- Move asset to different board
- Track and warn about expired links
async function revokeSharedAccess(assetToken) {
// Option 1: Move to private board
await updateAsset(assetToken, {
collection_token: "private-board",
});
// Option 2: Archive the asset
await updateAsset(assetToken, {
tags: [...asset.tags, "archived"],
});
console.log(`Access revoked for ${assetToken}`);
}
Best Practices
1. Descriptive Sharing
Include context when sharing:
async function shareWithContext(assetToken, recipient, message) {
const asset = await getAsset(assetToken);
const shareUrl = await shareAsset(assetToken);
await emailService.send({
to: recipient,
subject: `Shared Asset: ${asset.title}`,
body: `
${message}
Asset: ${asset.title}
Link: ${shareUrl}
Created: ${asset.created_at}
Tags: ${asset.tags.join(", ")}
This link will remain active until revoked.
`,
});
}
2. Organize Published Content
Use consistent naming for published boards:
const publishedBoards = {
portfolio: "main-portfolio",
client_work: "client-showcase",
team: "team-photos",
};
async function publishAll() {
for (const [name, token] of Object.entries(publishedBoards)) {
const { url } = await publishBoard(token);
console.log(`Published ${name}: ${url}`);
}
}
3. Watermark Shared Assets
Add watermarks to shared assets (implementation depends on your system):
async function shareWithWatermark(assetToken) {
// Get original asset
const asset = await getAsset(assetToken);
// Download and watermark
const watermarked = await addWatermark(asset.display_url, {
text: "CONFIDENTIAL",
opacity: 0.3,
});
// Upload watermarked version
const watermarkedAsset = await uploadAsset(watermarked);
// Share the watermarked version
return await shareAsset(watermarkedAsset.token);
}
4. Analytics and Monitoring
Track sharing activity:
class SharingAnalytics {
async logShare(type, token, recipient) {
await analytics.track({
event: "asset_shared",
properties: {
type,
token,
recipient,
timestamp: new Date(),
},
});
}
async getMostShared(days = 30) {
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
return await analytics.query({
event: "asset_shared",
since,
groupBy: "token",
orderBy: "count",
limit: 10,
});
}
}
Complete Sharing Workflow
Here's a complete implementation with email and tracking:
class PlaybookSharingManager {
constructor(orgSlug, accessToken) {
this.orgSlug = orgSlug;
this.accessToken = accessToken;
this.baseUrl = "https://api.playbook.com/v1";
}
async shareAsset(assetToken, options = {}) {
const response = await fetch(
`${this.baseUrl}/${this.orgSlug}/assets/${assetToken}/share`,
{
method: "POST",
headers: { Authorization: `Bearer ${this.accessToken}` },
},
);
const { data } = await response.json();
// Track the share
if (options.trackingId) {
await this.logShare("asset", assetToken, options.trackingId);
}
return data.url;
}
async shareBoard(boardToken, options = {}) {
const response = await fetch(
`${this.baseUrl}/${this.orgSlug}/boards/${boardToken}/share`,
{
method: "POST",
headers: { Authorization: `Bearer ${this.accessToken}` },
},
);
const { data } = await response.json();
if (options.trackingId) {
await this.logShare("board", boardToken, options.trackingId);
}
return data.url;
}
async publishBoard(boardToken) {
const response = await fetch(
`${this.baseUrl}/${this.orgSlug}/boards/${boardToken}/publish`,
{
method: "POST",
headers: { Authorization: `Bearer ${this.accessToken}` },
},
);
return await response.json();
}
async shareWithEmail(type, token, recipients, message) {
// Create share link
const url =
type === "asset"
? await this.shareAsset(token, { trackingId: recipients.join(",") })
: await this.shareBoard(token, { trackingId: recipients.join(",") });
// Get asset/board info
const info = await this.getInfo(type, token);
// Send emails
for (const recipient of recipients) {
await this.sendShareEmail(recipient, url, info, message);
}
return url;
}
async getInfo(type, token) {
const endpoint = type === "asset" ? "assets" : "boards";
const response = await fetch(
`${this.baseUrl}/${this.orgSlug}/${endpoint}/${token}`,
{ headers: { Authorization: `Bearer ${this.accessToken}` } },
);
const { data } = await response.json();
return data;
}
async sendShareEmail(recipient, url, info, message) {
// Implement email sending
console.log(`Sending to ${recipient}:`, {
url,
title: info.title,
message,
});
}
async logShare(type, token, trackingId) {
// Log to your analytics system
console.log("Share logged:", { type, token, trackingId });
}
}
// Usage
const manager = new PlaybookSharingManager("my-org", "your_token");
// Share asset with email
await manager.shareWithEmail(
"asset",
"product-photo",
["client@example.com", "stakeholder@example.com"],
"Please review this product photo and provide feedback.",
);
// Publish board
const published = await manager.publishBoard("portfolio");
console.log("Portfolio published at:", published.data.url);
Related API Endpoints
Next Steps
- Learn about Asset Management to organize shared content
- Explore Webhooks to track sharing events
- Read about Comments for collaboration on shared assets