From webflow-pack
Diagnoses and fixes Webflow Data API v2 errors (400, 401, 403, 404, 409, 429, 500) with causes, validation, token/scope checks for integration debugging.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin webflow-packThis skill is limited to using the following tools:
Quick reference for the most common Webflow Data API v2 errors, their root causes,
Executes Webflow API incident runbook: triages errors via curl checks on status page, HTTP codes, rate limits; activates circuit breakers, fallbacks; provides comms templates and postmortem.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Share bugs, ideas, or general feedback.
Quick reference for the most common Webflow Data API v2 errors, their root causes, and concrete solutions. Covers every HTTP status code the API returns.
webflow-api SDK installed{ "code": "validation_error", "message": "Invalid field data" }
Common causes:
name and slug are always required for CMS items)Fix:
// Check collection schema before creating items
const collection = await webflow.collections.get(collectionId);
const requiredFields = collection.fields?.filter(f => f.isRequired);
console.log("Required fields:", requiredFields?.map(f => `${f.slug} (${f.type})`));
// Validate slug format
function isValidSlug(slug: string): boolean {
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug);
}
{ "code": "unauthorized", "message": "Not authorized" }
Common causes:
Fix:
# Verify token works
curl -s https://api.webflow.com/v2/sites \
-H "Authorization: Bearer $WEBFLOW_API_TOKEN" \
-w "\nHTTP Status: %{http_code}\n"
# Check for whitespace issues
echo -n "$WEBFLOW_API_TOKEN" | wc -c
// Programmatic token check
async function verifyToken(): Promise<boolean> {
try {
await webflow.sites.list();
return true;
} catch (err: any) {
if (err.statusCode === 401) {
console.error("Token invalid. Generate new token at developers.webflow.com");
return false;
}
throw err;
}
}
{ "code": "forbidden", "message": "Insufficient permissions" }
Common causes:
cms:read)Fix:
// Required scopes by operation
const SCOPE_MAP: Record<string, string> = {
"sites.list": "sites:read",
"sites.publish": "sites:write",
"collections.list": "cms:read",
"collections.items.createItem": "cms:write",
"pages.list": "pages:read",
"forms.list": "forms:read",
"products.list": "ecommerce:read",
"products.create": "ecommerce:write",
"orders.list": "ecommerce:read",
"orders.refund": "ecommerce:write",
};
Generate a new token with the correct scopes at https://developers.webflow.com.
{ "code": "not_found", "message": "Resource not found" }
Common causes:
site_id, collection_id, or item_idFix:
// Discovery chain: always start from sites.list()
async function discoverResources() {
const { sites } = await webflow.sites.list();
console.log("Sites:", sites?.map(s => `${s.displayName}: ${s.id}`));
for (const site of sites!) {
const { collections } = await webflow.collections.list(site.id!);
console.log(` Collections in ${site.displayName}:`);
for (const col of collections!) {
console.log(` ${col.displayName}: ${col.id}`);
}
}
}
{ "code": "conflict", "message": "Item with slug already exists" }
Common causes:
Fix:
// Check for existing slug before creating
async function createOrUpdate(collectionId: string, slug: string, fieldData: Record<string, any>) {
const { items } = await webflow.collections.items.listItems(collectionId);
const existing = items?.find(i => i.fieldData?.slug === slug);
if (existing) {
return webflow.collections.items.updateItem(collectionId, existing.id!, { fieldData });
}
return webflow.collections.items.createItem(collectionId, { fieldData: { slug, ...fieldData } });
}
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Common causes:
Fix:
// The SDK auto-retries 429s with exponential backoff.
// For manual control:
async function waitForRateLimit(retryAfterSeconds: number) {
console.log(`Rate limited. Waiting ${retryAfterSeconds}s...`);
await new Promise(r => setTimeout(r, retryAfterSeconds * 1000));
}
See webflow-rate-limits for comprehensive rate limit handling.
Fix:
# 1. Check Webflow status
curl -s https://status.webflow.com/api/v2/status.json | jq '.status'
# 2. Retry with backoff (SDK handles this automatically)
# 3. If persistent, check Webflow status page and open support ticket
# Test API connectivity
curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $WEBFLOW_API_TOKEN" \
https://api.webflow.com/v2/sites
# Check rate limit headers
curl -v -H "Authorization: Bearer $WEBFLOW_API_TOKEN" \
https://api.webflow.com/v2/sites 2>&1 | grep -i "x-ratelimit\|retry-after"
# List sites (quick token verification)
curl -s -H "Authorization: Bearer $WEBFLOW_API_TOKEN" \
https://api.webflow.com/v2/sites | jq '.sites[].displayName'
# Check Webflow platform status
curl -s https://status.webflow.com/api/v2/status.json | jq '.status.description'
import { WebflowClient } from "webflow-api";
async function resilientCall<T>(
operation: () => Promise<T>,
label: string
): Promise<T> {
try {
return await operation();
} catch (err: any) {
const status = err.statusCode || err.status;
const actionMap: Record<number, string> = {
400: "Fix request payload — check field names and types",
401: "Rotate token at developers.webflow.com",
403: "Add missing scope to token",
404: "Verify resource IDs with discovery chain",
409: "Handle duplicate — update instead of create",
429: "Wait for Retry-After header (SDK auto-retries)",
500: "Webflow server error — retry later",
};
console.error(`[${label}] HTTP ${status}: ${actionMap[status] || "Unknown error"}`);
console.error(` Message: ${err.message}`);
console.error(` Body: ${JSON.stringify(err.body)}`);
throw err;
}
}
webflow-debug-bundleFor comprehensive debugging, see webflow-debug-bundle.