Claude-code-plugins miro-common-errors
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/miro-pack/skills/miro-common-errors" ~/.claude/skills/jeremylongshore-claude-code-plugins-miro-common-errors && rm -rf "$T"
manifest:
plugins/saas-packs/miro-pack/skills/miro-common-errors/SKILL.mdsource content
Miro Common Errors
Overview
Quick reference for Miro REST API v2 errors organized by HTTP status code, with real error response bodies and proven fixes.
Prerequisites
- Access token configured
available for diagnostic requestscurl
Quick Diagnostic
# 1. Verify API connectivity curl -s -o /dev/null -w "%{http_code}" https://api.miro.com/v2/boards \ -H "Authorization: Bearer $MIRO_ACCESS_TOKEN" # 2. Check token validity curl -s https://api.miro.com/v1/oauth-token \ -H "Authorization: Bearer $MIRO_ACCESS_TOKEN" | jq # 3. Check Miro status page curl -s https://status.miro.com/api/v2/status.json | jq '.status.description'
Error Reference
400 — Bad Request
{ "status": 400, "code": "invalidInput", "message": "Could not resolve the value for parameter: data.content", "context": { "fields": [{ "field": "data.content", "message": "Required" }] } }
Common causes:
- Missing required fields in request body
- Wrong data types (string instead of number for position)
- Invalid enum values (e.g.,
— correct isshape: 'oval'
)shape: 'circle'
Fix: Cross-reference your request body with the REST API reference. Each item type has specific required fields.
Sticky note required fields:
data.content, data.shape (square or rectangle)
Shape required fields: data.shape (see miro-sdk-patterns for valid shapes)
Connector required fields: startItem.id, endItem.id
401 — Unauthorized
{ "status": 401, "code": "tokenNotProvided", "message": "Access token is not provided" }
{ "status": 401, "code": "tokenExpired", "message": "Access token has expired" }
Common causes:
- Missing
headerAuthorization: Bearer <token> - Access token expired (tokens last 3599 seconds / ~1 hour)
- Using client_id/client_secret instead of access_token
Fix:
# Check if token is set echo "Token length: ${#MIRO_ACCESS_TOKEN}" # Refresh expired token curl -X POST https://api.miro.com/v1/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token" \ -d "client_id=$MIRO_CLIENT_ID" \ -d "client_secret=$MIRO_CLIENT_SECRET" \ -d "refresh_token=$MIRO_REFRESH_TOKEN"
403 — Forbidden
{ "status": 403, "code": "insufficientPermissions", "message": "Required scopes: boards:write", "context": { "requiredScopes": ["boards:write"] } }
Common causes:
- Token lacks required OAuth scope
- User does not have board-level permission (viewer trying to write)
- Team-level restrictions prevent the operation
Fix:
- Check which scopes your token has vs. what the endpoint requires
- Update scopes in your Miro app settings at https://developers.miro.com
- Re-authorize the user to get a token with updated scopes
| Endpoint Category | Required Scope |
|---|---|
| GET boards/items | |
| POST/PATCH/DELETE boards/items | |
| GET team/members | |
| Organization endpoints | |
404 — Not Found
{ "status": 404, "code": "boardNotFound", "message": "Board not found or access denied" }
Common causes:
- Board ID is wrong or has been deleted
- Item ID references a deleted item
- Token owner does not have access to the board
Fix:
# Verify board exists and you have access curl -s https://api.miro.com/v2/boards/$BOARD_ID \ -H "Authorization: Bearer $MIRO_ACCESS_TOKEN" | jq '.id, .name' # List boards to find correct ID curl -s "https://api.miro.com/v2/boards?limit=10" \ -H "Authorization: Bearer $MIRO_ACCESS_TOKEN" | jq '.data[] | {id, name}'
409 — Conflict
{ "status": 409, "code": "duplicateTagTitle", "message": "A tag with this title already exists" }
Common causes:
- Creating a tag with a title that already exists on the board
- Concurrent modifications to the same item
Fix: Fetch existing tags first and reuse their IDs instead of creating duplicates.
429 — Rate Limited
{ "status": 429, "code": "rateLimitExceeded", "message": "Rate limit exceeded" }
Response headers:
X-RateLimit-Limit: 100000 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1700000060 Retry-After: 30
Fix: Honor the
Retry-After header. See miro-rate-limits for complete backoff patterns. The global limit is 100,000 credits/minute.
500 / 502 / 503 — Server Error
Common causes:
- Miro platform issue (check https://status.miro.com)
- Transient infrastructure error
Fix:
- Check Miro status page
- Retry with exponential backoff (see
)miro-rate-limits - If persistent (>5 min), it is a Miro-side issue — enable fallback mode
Programmatic Error Handler
async function handleMiroError(response: Response, context: string): Promise<never> { const body = await response.json().catch(() => ({})); switch (response.status) { case 401: console.error(`[Miro:${context}] Token expired/invalid. Refreshing...`); // Trigger token refresh break; case 403: console.error(`[Miro:${context}] Missing scopes: ${body.context?.requiredScopes?.join(', ')}`); break; case 429: const retryAfter = response.headers.get('Retry-After') ?? '60'; console.warn(`[Miro:${context}] Rate limited. Retry after ${retryAfter}s`); break; default: console.error(`[Miro:${context}] ${response.status}: ${body.message ?? 'Unknown error'}`); } throw new Error(`Miro API ${response.status}: ${body.message ?? context}`); }
Escalation Path
- Run diagnostics above
- Collect evidence with
miro-debug-bundle - Check https://status.miro.com
- File support ticket with request ID (from
response header)X-Request-Id
Resources
Next Steps
For comprehensive debugging, see
miro-debug-bundle.