HoplaDocs

Errors and rate limits

API rate limits, error format and error codes.

Rate limits

ResourceLimit
Requests per key120 per minute, 5,000 per day
Failed authentications per IP address20 per minute
Request body256 KB; 2.2 MB for a data URL image and for an MCP message
Keys per account10
Web fetching (/fetch/preview, /fetch/feed, /fetch/rating)30 per 10 minutes per account
Newsletter sync (/subscribers/sync)20 per 10 minutes per account
Image (/media)1.5 MB, WebP, JPEG or PNG

When a rate limit is exceeded, the API returns 429 with a Retry-After header: wait that many seconds before retrying.

Retry after a 429
async function request(url, options, retries = 3) {
  const response = await fetch(url, options);
  if (response.status === 429 && retries > 0) {
    const seconds = Number(response.headers.get('Retry-After') ?? 60);
    await new Promise(r => setTimeout(r, seconds * 1000));
    return request(url, options, retries - 1);
  }
  return response;
}

Error format

Every error returns an HTTP status and an error object:

{ "error": { "code": "invalid_api_key", "message": "Invalid or revoked API key." } }

Use code in your error handling logic: its value is stable. message is a human-readable description, in English, and may change.

Error codes

StatuscodeDescription
400invalid_jsonRequest body is not valid JSON
400empty_updatePATCH /settings without any setting
400unknown_themeUnknown theme: see GET /themes
400unknown_block_typeUnknown block type
400block_type_unavailableBlock type not available to this account
400block_rejectedBlock rejected by validation, for example scheduling without the Starter plan
400invalid_orderPUT /blocks/order without every id, or with a duplicate id
400invalid_periodInvalid period, or outside the analytics retention
400invalid_urlBlock url cannot be parsed (https://, mailto: or tel: expected), or a web fetching request without an https:// URL
400missing_imagePOST /media without an image
401missing_api_keyMissing Authorization: Bearer hopla_… header
401invalid_api_keyInvalid or revoked key
403read_only_keyThe key is read-only
403plan_requiredThe plan does not include this feature (API: Pro; analytics: Starter)
403terms_not_acceptedUpdated terms of service must be accepted on app.hopla.page
403account_suspendedAccount suspended
404block_not_foundBlock not found
404subscriber_not_foundSubscriber not found
404not_foundRoute not found
405method_not_allowedMethod not allowed on this route
409no_integrationPOST /subscribers/sync without a connected newsletter or webhook
413payload_too_largeRequest body over 256 KB (2.2 MB for an image)
413image_too_largeImage over 1.5 MB
413storage_quota_exceededImage storage quota exceeded for this account
415unsupported_image_formatImage format other than WebP, JPEG or PNG
422fetch_failedThe site did not respond, or did not return HTML
429rate_limitedRate limit exceeded for this key, or web fetching rate limit exceeded
429too_many_invalid_keysToo many failed authentications from this IP address
500internal_errorInternal server error: retry shortly

The MCP server returns the same errors: the tool result has isError: true, with the message as text.

API v1 returns the same statuses with an erreur object and French codes (cle_invalide, bloc_introuvable…): see Migrating from v1.

On this page