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
curlavailable for diagnostic requests
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.,
shape: 'oval'— correct isshape: '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
Authorization: Bearer <token>header - 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 | boards:read |
| POST/PATCH/DELETE boards/items | boards:write |
| GET team/members | team:read |
| Organization endpoints | organizations:read |
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
X-Request-Idresponse header)
Resources
Next Steps
For comprehensive debugging, see miro-debug-bundle.