Error format
Every error response uses one JSON envelope. Branch on code, never on message. Send the request_id to support when you need help. See pagination, rate limits and idempotency for retries.
{
"error": {
"type": "validation_error",
"code": "slug_taken",
"message": "The slug \"diwali\" is already in use on crop.sh.",
"param": "slug",
"request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
"doc_url": "https://urlcrop.com/docs/api/errors#slug_taken",
"details": [
{ "path": "slug", "code": "slug_taken", "message": "Already in use" }
]
}
}invalid_request_error
authentication_error
unauthenticatedHTTP 401
Authentication required
The single 401 code. details[0].code gives the reason: missing_credentials, invalid_api_key, api_key_revoked, api_key_expired, session_expired, oauth_grant_revoked, oauth_token_expired, publishable_key_not_allowed (a uc_pub_ key on anything but POST /v1/collect), internal_signature_invalid
permission_error
two_factor_requiredHTTP 403
Two-factor authentication required
TOTP enabled, second factor not completed for this session; or (param: two_factor, details[0].code = two_factor_grace_expired, details[0].grace_ends_at) a session write by an Owner/Admin on Business, Agency or Enterprise without 2FA after the 14-day grace. Reads, API keys and redirects are unaffected
step_up_requiredHTTP 403
Recent re-authentication required
The action needs a fresh authentication; the dashboard opens the re-authentication prompt and retries
email_verification_requiredHTTP 403
Email verification required
Unverified account on any create route (links, QR, pages, domains, API keys, invitations), on POST /v1/invitations/accept or on a billing route; the response carries a resend hint.
signups_pausedHTTP 403
New sign-ups are paused right now. Join the waitlist or check back soon.
New sign-ups are paused (platform_settings.signups_paused) on /sign-up/email and first OAuth sign-in; existing accounts sign in normally.
insufficient_scopeHTTP 403
API key scope missing
Key lacks the scope; param = the required action name (e.g. links.create)
insufficient_roleHTTP 403
Role does not allow this action
Role does not allow the action (a non-member gets 404 resource_not_found)
workspace_mismatchHTTP 403
Workspace does not match the credential
X-Workspace-Id differs from the key's workspace
wrong_environmentHTTP 403
Credential used in the wrong environment
Test-mode credential on a live-only route (/v1/billing/checkout-sessions with real payment) or live credential on /v1/test/*
write_blocked_past_dueHTTP 403
Workspace is read-only (payment past due)
Dunning day 7+: workspace read-only, no new links/QR/pages; reads still succeed
content_policy_holdHTTP 403
Page is on a content policy hold
A page held for a content policy violation cannot be republished by its owner; the hold is lifted by staff only
second_approver_requiredHTTP 403
A different staff member must approve this action
Staff console only: the action needs a second, different staff member to approve it (duplicate-charge correction, staff role grant, kill switch on a paid workspace), and the caller requested it or no other eligible approver exists
view_as_read_onlyHTTP 403
Support view is read-only
A write was attempted during a read-only support view of the workspace; the support session can only read
support_access_approval_requiredHTTP 403
Customer approval needed
Staff console only: "view as" refused because the Enterprise Organization set support_access = 'on_request' and the workspace has no live customer grant (/v1/workspaces/{id}/support-access/grants)
plan_limit_error
feature_not_in_planHTTP 403
Feature not in plan
Feature absent from the plan: API on Free/Starter, bulk below Pro, Convert below Business…; param = plan limit key (api_requests_per_day, bulk_csv, convert, smart_routing, retargeting_pixels, link_controls, custom_slugs, custom_domains, …)
plan_limit_reachedHTTP 403
Plan limit reached
A numeric entitlement is exhausted; param is plan limit key: links_per_month, dynamic_qr_total, pages_total, custom_domains, seats, client_workspaces, webhook_endpoints, events_per_month, or downgrade (resource above the new plan's limit is read-only). details[0] carries limit, used, resets_at and upgrade_to
not_found_error
resource_not_foundHTTP 404
Resource not found
Id does not exist **or belongs to another workspace** (never distinguished); also a session X-Workspace-Id the user is not an active member of
conflict_error
domain_already_existsHTTP 409
Domain already registered
Hostname registered (by this or another workspace)
idempotency_in_progressHTTP 409
Request with this idempotency key in progress
Same Idempotency-Key executing
state_conflictHTTP 409
Invalid for the current state
Invalid for current state (publish a published page, verify an active domain, cancel a canceled subscription, erase while owning workspaces, restore a workspace after its 30-day window, transfer ownership to a member who already owns a workspace, attach a QR code to a link that already has a live one — param: link_id)
resource_in_useHTTP 409
Resource in use
Delete blocked: a domain with active links unless ?force=true; an asset used by a live QR code (no force)
validation_error
destination_invalidHTTP 422
Destination URL not allowed
Not absolute http(s), private IP, credentials in the URL, too long, or points at a URLCrop domain; details[0].code gives the reason (unparseable, scheme_not_allowed, has_credentials, not_public, is_urlcrop, too_long)
destination_blockedHTTP 422
Destination blocked by link safety
Safety: denylist entry, feed hit or a fresh malicious verdict only; unknown risk creates the link as pending instead. A content-policy list hit (adult, gambling, hacking, csam/terror) adds details[0].code: policy_violation with param set to the category
destination_unreachableHTTP 422
Destination unreachable
Health pre-check failed with require_reachable=true
domain_proxiedHTTP 422
Domain must be DNS-only
Hostname resolves to Cloudflare proxy IPs (orange cloud) instead of the VPS; must be DNS-only
unsupported_qr_typeHTTP 422
QR type not supported
QR type not in this phase/plan (Phase 2 types google_review, menu, app_store, sms, email)
qr_unreadableHTTP 422
QR code would not scan
The QR design failed the server decode test (contrast, logo coverage or shapes); returned by render requests; the code is saved, but no render is produced until the design is fixed
attribution_key_missingHTTP 422
Attribution key missing
Conversion without click_id, coupon_code or customer
policy_acceptance_requiredHTTP 422
No-refund policy acceptance required
Checkout without accept_no_refund_policy: true and a current policy_version
rate_limit_error
rate_limitedHTTP 429
Rate limit exceeded
The single 429 code, always with Retry-After. param tells which limiter: burst (per-plan req/s), daily_quota (plan day quota and packs exhausted), endpoint (per-endpoint, per-IP or publishable-key limit), velocity, exports
api_error
upstream_unavailableHTTP 502
Upstream service unavailable
Payment provider / Web Risk / DNS resolver unavailable for a synchronous call
service_unavailableHTTP 503
Service unavailable
Maintenance or dependency outage; a QR render not ready within 8 s
timeoutHTTP 504
Operation timed out
Synchronous operation exceeded 25 s (well under Cloudflare's 100 s)