Skip to content

Jobs

Jobs endpoints of the URLCrop API: 2 operations. Progress and results of bulk link creation and CSV imports.

Bulk job progress and row errors

GET/jobs/{id}

  • Scope links:read

Status of a POST /v1/links/bulk or POST /v1/links/import job. progress.done counts finished rows (created or failed), progress.failed the failed ones. errors lists the first 100 failed rows (index is the 0-based position in the submitted array or among the CSV data rows). status: paused means the workspace's creation velocity was reached (notice, resume_at); the job resumes by itself. Poll every 2–5 seconds. Readable with links:read or links:write. Another workspace's job is 404.

Parameters

NameInTypeDescription
idrequiredpathstringPrefixed ULID (<prefix>_<26 Crockford base32 chars>).
X-Workspace-IdheaderstringWorkspace to act on. Required for session requests on workspace-scoped routes; optional for API keys (must equal the key's workspace, otherwise 403 workspace_mismatch). For a session, a workspace the user is not an active member of (another tenant, a removed membership, a malformed or unknown id) answers 404 resource_not_found, never 403.

Request example

GET /jobs/{id}cURL
curl 'https://api.urlcrop.com/v1/jobs/lnk_01JB4N8ZK3Q9WXYZ0123456789' \
  -H 'Authorization: Bearer uc_live_…'

Responses

  • 200 response

    Job

    Job schema and example
    FieldTypeDescription
    idrequiredstring
    objectrequiredstringValue: job
    kindrequiredstringOne of: links.bulk, links.import
    statusrequiredstringOne of: queued, processing, paused, completed, failed
    progressrequiredobject
    progress.totalrequiredinteger
    progress.donerequiredintegerFinished rows, created or failed.
    progress.failedrequiredinteger
    noticerequiredstring | nullWhy the job is paused or failed, for display.
    resume_atrequiredstring | nullFormat: date-time
    results_urlrequiredstring | nullGET /v1/jobs/{id}/results once the job has finished.Format: uri
    errorsrequiredarray of objectmax items 100
    errors[].indexrequiredinteger
    errors[].coderequiredstring
    errors[].messagerequiredstring
    errors[].paramrequiredstring | null
    created_atrequiredstringISO 8601 UTC with milliseconds.Format: date-time
    started_atrequiredstring | nullFormat: date-time
    completed_atrequiredstring | nullFormat: date-time
    200 response: 200 responseJSON
    {
      "id": "job_01JB4PR6S7T8U9V0W1X2Y3Z4A5",
      "object": "job",
      "kind": "links.bulk",
      "status": "queued",
      "progress": {
        "total": 0,
        "done": 0,
        "failed": 0
      },
      "notice": "string",
      "resume_at": "2026-12-05T10:15:30.000Z",
      "results_url": "https://example.com",
      "errors": [
        {
          "index": 0,
          "code": "string",
          "message": "string",
          "param": "string"
        }
      ],
      "created_at": "2026-12-05T10:15:30.000Z",
      "started_at": "2026-12-05T10:15:30.000Z",
      "completed_at": "2026-12-05T10:15:30.000Z"
    }
    Example generated from the schema.
  • 400 response

    Malformed request (invalid_json, invalid_query, missing_header, invalid_cursor, invalid_sort, invalid_expand). Any credentialed operation can return it: a credential in the query string (api_key, access_token) is invalid_query, and a session request without X-Workspace-Id on a workspace-scoped route is missing_header.

    Error codes: invalid_json, invalid_query, missing_header, invalid_cursor, invalid_sort, invalid_expand

    Error schema and example
    FieldTypeDescription
    errorrequiredobject
    error.typerequiredstringOne of: invalid_request_error, authentication_error, permission_error, plan_limit_error, not_found_error, conflict_error, validation_error, rate_limit_error, api_error
    error.coderequiredstringComplete error-code catalogue. @urlcrop/shared/errors is tested against this list. param on plan_limit_error responses is always a plan limit key.One of: invalid_json, invalid_query, missing_header, invalid_cursor, invalid_sort, invalid_expand, unauthenticated, two_factor_required, step_up_required, email_verification_required, signups_paused, insufficient_scope, insufficient_role, workspace_mismatch, wrong_environment, workspace_suspended, write_blocked_past_due, content_policy_hold, origin_not_allowed, turnstile_failed, second_approver_required, view_as_read_only, support_access_approval_required, feature_not_in_plan, plan_limit_reached, resource_not_found, route_not_found, slug_taken, domain_already_exists, member_already_exists, invitation_pending, idempotency_in_progress, state_conflict, resource_in_use, payload_too_large, unsupported_media_type, validation_failed, destination_invalid, destination_blocked, destination_unreachable, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, invalid_routing_rule, invalid_currency, csv_invalid, attribution_key_missing, policy_acceptance_required, rate_limited, internal_error, upstream_unavailable, service_unavailable, timeout
    error.messagerequiredstringHuman-readable, safe to show to end users.
    error.paramstringField, header or scope the error refers to, when applicable.
    error.request_idrequiredstring
    error.doc_urlrequiredstringFormat: uri
    error.detailsarray of object
    error.details[].pathstring
    error.details[].coderequiredstringZod issue code, unsupported_in_phase, a destination safety reason, or a 401 reason.
    error.details[].messagerequiredstring
    error.details[].limitinteger | nullPlan limit in force (plan_limit_error only; null = unlimited).
    error.details[].usedintegerCurrent usage (plan_limit_error only).
    error.details[].resets_atstringWhen a periodic limit resets (plan_limit_error only).Format: date-time
    error.details[].upgrade_tostringPlan slug that lifts a plan limit (plan_limit_error only).
    400 response: missing headerJSON
    {
      "error": {
        "type": "invalid_request_error",
        "code": "missing_header",
        "message": "The X-Workspace-Id header is required for session requests.",
        "param": "X-Workspace-Id",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#missing_header"
      }
    }
  • 401 response

    Missing or invalid credentials. Always code: unauthenticated; details[0].code gives the reason (missing_credentials, invalid_api_key, api_key_revoked, api_key_expired, session_expired, publishable_key_not_allowed).

    Error codes: unauthenticated

    Error schema and example
    FieldTypeDescription
    errorrequiredobject
    error.typerequiredstringOne of: invalid_request_error, authentication_error, permission_error, plan_limit_error, not_found_error, conflict_error, validation_error, rate_limit_error, api_error
    error.coderequiredstringComplete error-code catalogue. @urlcrop/shared/errors is tested against this list. param on plan_limit_error responses is always a plan limit key.One of: invalid_json, invalid_query, missing_header, invalid_cursor, invalid_sort, invalid_expand, unauthenticated, two_factor_required, step_up_required, email_verification_required, signups_paused, insufficient_scope, insufficient_role, workspace_mismatch, wrong_environment, workspace_suspended, write_blocked_past_due, content_policy_hold, origin_not_allowed, turnstile_failed, second_approver_required, view_as_read_only, support_access_approval_required, feature_not_in_plan, plan_limit_reached, resource_not_found, route_not_found, slug_taken, domain_already_exists, member_already_exists, invitation_pending, idempotency_in_progress, state_conflict, resource_in_use, payload_too_large, unsupported_media_type, validation_failed, destination_invalid, destination_blocked, destination_unreachable, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, invalid_routing_rule, invalid_currency, csv_invalid, attribution_key_missing, policy_acceptance_required, rate_limited, internal_error, upstream_unavailable, service_unavailable, timeout
    error.messagerequiredstringHuman-readable, safe to show to end users.
    error.paramstringField, header or scope the error refers to, when applicable.
    error.request_idrequiredstring
    error.doc_urlrequiredstringFormat: uri
    error.detailsarray of object
    error.details[].pathstring
    error.details[].coderequiredstringZod issue code, unsupported_in_phase, a destination safety reason, or a 401 reason.
    error.details[].messagerequiredstring
    error.details[].limitinteger | nullPlan limit in force (plan_limit_error only; null = unlimited).
    error.details[].usedintegerCurrent usage (plan_limit_error only).
    error.details[].resets_atstringWhen a periodic limit resets (plan_limit_error only).Format: date-time
    error.details[].upgrade_tostringPlan slug that lifts a plan limit (plan_limit_error only).
    401 response: unauthenticatedJSON
    {
      "error": {
        "type": "authentication_error",
        "code": "unauthenticated",
        "message": "The API key is not valid.",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#unauthenticated",
        "details": [
          {
            "path": "Authorization",
            "code": "invalid_api_key",
            "message": "No key matches this secret"
          }
        ]
      }
    }
  • 403 response

    Permitted credential but not allowed: insufficient_scope, insufficient_role, workspace_mismatch, wrong_environment, two_factor_required (also Owner/Admin writes on Business+ after the 14-day 2FA grace, details[0].grace_ends_at), step_up_required, email_verification_required, signups_paused (sign-up while the platform setting is on), write_blocked_past_due, workspace_suspended, content_policy_hold, origin_not_allowed, or a plan limit (type: plan_limit_error — feature_not_in_plan, plan_limit_reached with param naming the plan limit key). Any credentialed operation can return it: an API key on a session-only route is insufficient_scope, a session write without an allowed Origin is origin_not_allowed, and an API key whose plan has no API access is feature_not_in_plan.

    Error codes: two_factor_required, step_up_required, email_verification_required, signups_paused, insufficient_scope, insufficient_role, workspace_mismatch, wrong_environment, workspace_suspended, write_blocked_past_due, content_policy_hold, origin_not_allowed, feature_not_in_plan, plan_limit_reached

    Error schema and example
    FieldTypeDescription
    errorrequiredobject
    error.typerequiredstringOne of: invalid_request_error, authentication_error, permission_error, plan_limit_error, not_found_error, conflict_error, validation_error, rate_limit_error, api_error
    error.coderequiredstringComplete error-code catalogue. @urlcrop/shared/errors is tested against this list. param on plan_limit_error responses is always a plan limit key.One of: invalid_json, invalid_query, missing_header, invalid_cursor, invalid_sort, invalid_expand, unauthenticated, two_factor_required, step_up_required, email_verification_required, signups_paused, insufficient_scope, insufficient_role, workspace_mismatch, wrong_environment, workspace_suspended, write_blocked_past_due, content_policy_hold, origin_not_allowed, turnstile_failed, second_approver_required, view_as_read_only, support_access_approval_required, feature_not_in_plan, plan_limit_reached, resource_not_found, route_not_found, slug_taken, domain_already_exists, member_already_exists, invitation_pending, idempotency_in_progress, state_conflict, resource_in_use, payload_too_large, unsupported_media_type, validation_failed, destination_invalid, destination_blocked, destination_unreachable, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, invalid_routing_rule, invalid_currency, csv_invalid, attribution_key_missing, policy_acceptance_required, rate_limited, internal_error, upstream_unavailable, service_unavailable, timeout
    error.messagerequiredstringHuman-readable, safe to show to end users.
    error.paramstringField, header or scope the error refers to, when applicable.
    error.request_idrequiredstring
    error.doc_urlrequiredstringFormat: uri
    error.detailsarray of object
    error.details[].pathstring
    error.details[].coderequiredstringZod issue code, unsupported_in_phase, a destination safety reason, or a 401 reason.
    error.details[].messagerequiredstring
    error.details[].limitinteger | nullPlan limit in force (plan_limit_error only; null = unlimited).
    error.details[].usedintegerCurrent usage (plan_limit_error only).
    error.details[].resets_atstringWhen a periodic limit resets (plan_limit_error only).Format: date-time
    error.details[].upgrade_tostringPlan slug that lifts a plan limit (plan_limit_error only).
    403 response: plan limit reachedJSON
    {
      "error": {
        "type": "plan_limit_error",
        "code": "plan_limit_reached",
        "message": "You have created 2,000 of 2,000 links this billing period. Upgrade or buy a pack to continue.",
        "param": "links_per_month",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/pricing",
        "details": [
          {
            "code": "plan_limit_reached",
            "message": "Monthly link quota reached",
            "limit": 2000,
            "used": 2000,
            "resets_at": "2026-12-01T00:00:00.000Z",
            "upgrade_to": "business"
          }
        ]
      }
    }
    403 response: feature not in planJSON
    {
      "error": {
        "type": "plan_limit_error",
        "code": "feature_not_in_plan",
        "message": "The public API is available on the Pro plan and above.",
        "param": "api_requests_per_day",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/pricing",
        "details": [
          {
            "code": "feature_not_in_plan",
            "message": "Upgrade to Pro",
            "upgrade_to": "pro"
          }
        ]
      }
    }
    403 response: email verification requiredJSON
    {
      "error": {
        "type": "permission_error",
        "code": "email_verification_required",
        "message": "Verify your email address to create links.",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#email_verification_required"
      }
    }
    403 response: step up requiredJSON
    {
      "error": {
        "type": "permission_error",
        "code": "step_up_required",
        "message": "Confirm your password or 2FA code to continue.",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#step_up_required"
      }
    }
  • 404 response

    Unknown id, or the resource belongs to another workspace (never distinguished). Also returned when a session's X-Workspace-Id names a workspace the user is not an active member of.

    Error codes: resource_not_found

    Error schema and example
    FieldTypeDescription
    errorrequiredobject
    error.typerequiredstringOne of: invalid_request_error, authentication_error, permission_error, plan_limit_error, not_found_error, conflict_error, validation_error, rate_limit_error, api_error
    error.coderequiredstringComplete error-code catalogue. @urlcrop/shared/errors is tested against this list. param on plan_limit_error responses is always a plan limit key.One of: invalid_json, invalid_query, missing_header, invalid_cursor, invalid_sort, invalid_expand, unauthenticated, two_factor_required, step_up_required, email_verification_required, signups_paused, insufficient_scope, insufficient_role, workspace_mismatch, wrong_environment, workspace_suspended, write_blocked_past_due, content_policy_hold, origin_not_allowed, turnstile_failed, second_approver_required, view_as_read_only, support_access_approval_required, feature_not_in_plan, plan_limit_reached, resource_not_found, route_not_found, slug_taken, domain_already_exists, member_already_exists, invitation_pending, idempotency_in_progress, state_conflict, resource_in_use, payload_too_large, unsupported_media_type, validation_failed, destination_invalid, destination_blocked, destination_unreachable, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, invalid_routing_rule, invalid_currency, csv_invalid, attribution_key_missing, policy_acceptance_required, rate_limited, internal_error, upstream_unavailable, service_unavailable, timeout
    error.messagerequiredstringHuman-readable, safe to show to end users.
    error.paramstringField, header or scope the error refers to, when applicable.
    error.request_idrequiredstring
    error.doc_urlrequiredstringFormat: uri
    error.detailsarray of object
    error.details[].pathstring
    error.details[].coderequiredstringZod issue code, unsupported_in_phase, a destination safety reason, or a 401 reason.
    error.details[].messagerequiredstring
    error.details[].limitinteger | nullPlan limit in force (plan_limit_error only; null = unlimited).
    error.details[].usedintegerCurrent usage (plan_limit_error only).
    error.details[].resets_atstringWhen a periodic limit resets (plan_limit_error only).Format: date-time
    error.details[].upgrade_tostringPlan slug that lifts a plan limit (plan_limit_error only).
    404 response: not foundJSON
    {
      "error": {
        "type": "not_found_error",
        "code": "resource_not_found",
        "message": "No link with id lnk_01JB4N8ZK3Q9WXYZ0123456789.",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#resource_not_found"
      }
    }
  • 429 response

    Burst limit, daily plan quota or endpoint limit exceeded.

    Error codes: rate_limited

    Error schema and example
    FieldTypeDescription
    errorrequiredobject
    error.typerequiredstringOne of: invalid_request_error, authentication_error, permission_error, plan_limit_error, not_found_error, conflict_error, validation_error, rate_limit_error, api_error
    error.coderequiredstringComplete error-code catalogue. @urlcrop/shared/errors is tested against this list. param on plan_limit_error responses is always a plan limit key.One of: invalid_json, invalid_query, missing_header, invalid_cursor, invalid_sort, invalid_expand, unauthenticated, two_factor_required, step_up_required, email_verification_required, signups_paused, insufficient_scope, insufficient_role, workspace_mismatch, wrong_environment, workspace_suspended, write_blocked_past_due, content_policy_hold, origin_not_allowed, turnstile_failed, second_approver_required, view_as_read_only, support_access_approval_required, feature_not_in_plan, plan_limit_reached, resource_not_found, route_not_found, slug_taken, domain_already_exists, member_already_exists, invitation_pending, idempotency_in_progress, state_conflict, resource_in_use, payload_too_large, unsupported_media_type, validation_failed, destination_invalid, destination_blocked, destination_unreachable, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, invalid_routing_rule, invalid_currency, csv_invalid, attribution_key_missing, policy_acceptance_required, rate_limited, internal_error, upstream_unavailable, service_unavailable, timeout
    error.messagerequiredstringHuman-readable, safe to show to end users.
    error.paramstringField, header or scope the error refers to, when applicable.
    error.request_idrequiredstring
    error.doc_urlrequiredstringFormat: uri
    error.detailsarray of object
    error.details[].pathstring
    error.details[].coderequiredstringZod issue code, unsupported_in_phase, a destination safety reason, or a 401 reason.
    error.details[].messagerequiredstring
    error.details[].limitinteger | nullPlan limit in force (plan_limit_error only; null = unlimited).
    error.details[].usedintegerCurrent usage (plan_limit_error only).
    error.details[].resets_atstringWhen a periodic limit resets (plan_limit_error only).Format: date-time
    error.details[].upgrade_tostringPlan slug that lifts a plan limit (plan_limit_error only).
    429 response: rate limitedJSON
    {
      "error": {
        "type": "rate_limit_error",
        "code": "rate_limited",
        "message": "Daily API quota of 50,000 requests exhausted. Resets at 00:00 UTC.",
        "param": "daily_quota",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/rate-limits"
      }
    }

Per-row results as CSV

GET/jobs/{id}/results

  • Scope links:read

text/csv with one line per submitted row: row (1-based), status (created, failed or pending), short_url, link_id, destination_url, slug, error_code, error_message. Cells starting with =, +, -, @, tab or carriage return are prefixed with ' so a spreadsheet never evaluates them.

Parameters

NameInTypeDescription
idrequiredpathstringPrefixed ULID (<prefix>_<26 Crockford base32 chars>).
X-Workspace-IdheaderstringWorkspace to act on. Required for session requests on workspace-scoped routes; optional for API keys (must equal the key's workspace, otherwise 403 workspace_mismatch). For a session, a workspace the user is not an active member of (another tenant, a removed membership, a malformed or unknown id) answers 404 resource_not_found, never 403.

Request example

GET /jobs/{id}/resultscURL
curl 'https://api.urlcrop.com/v1/jobs/lnk_01JB4N8ZK3Q9WXYZ0123456789/results' \
  -H 'Authorization: Bearer uc_live_…'

Responses

  • 200 response

    Results CSV

    Body schema and example
    200 response: 200 responseJSON
    "string"
    Example generated from the schema.
  • 400 response

    Malformed request (invalid_json, invalid_query, missing_header, invalid_cursor, invalid_sort, invalid_expand). Any credentialed operation can return it: a credential in the query string (api_key, access_token) is invalid_query, and a session request without X-Workspace-Id on a workspace-scoped route is missing_header.

    Error codes: invalid_json, invalid_query, missing_header, invalid_cursor, invalid_sort, invalid_expand

    Error schema and example
    FieldTypeDescription
    errorrequiredobject
    error.typerequiredstringOne of: invalid_request_error, authentication_error, permission_error, plan_limit_error, not_found_error, conflict_error, validation_error, rate_limit_error, api_error
    error.coderequiredstringComplete error-code catalogue. @urlcrop/shared/errors is tested against this list. param on plan_limit_error responses is always a plan limit key.One of: invalid_json, invalid_query, missing_header, invalid_cursor, invalid_sort, invalid_expand, unauthenticated, two_factor_required, step_up_required, email_verification_required, signups_paused, insufficient_scope, insufficient_role, workspace_mismatch, wrong_environment, workspace_suspended, write_blocked_past_due, content_policy_hold, origin_not_allowed, turnstile_failed, second_approver_required, view_as_read_only, support_access_approval_required, feature_not_in_plan, plan_limit_reached, resource_not_found, route_not_found, slug_taken, domain_already_exists, member_already_exists, invitation_pending, idempotency_in_progress, state_conflict, resource_in_use, payload_too_large, unsupported_media_type, validation_failed, destination_invalid, destination_blocked, destination_unreachable, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, invalid_routing_rule, invalid_currency, csv_invalid, attribution_key_missing, policy_acceptance_required, rate_limited, internal_error, upstream_unavailable, service_unavailable, timeout
    error.messagerequiredstringHuman-readable, safe to show to end users.
    error.paramstringField, header or scope the error refers to, when applicable.
    error.request_idrequiredstring
    error.doc_urlrequiredstringFormat: uri
    error.detailsarray of object
    error.details[].pathstring
    error.details[].coderequiredstringZod issue code, unsupported_in_phase, a destination safety reason, or a 401 reason.
    error.details[].messagerequiredstring
    error.details[].limitinteger | nullPlan limit in force (plan_limit_error only; null = unlimited).
    error.details[].usedintegerCurrent usage (plan_limit_error only).
    error.details[].resets_atstringWhen a periodic limit resets (plan_limit_error only).Format: date-time
    error.details[].upgrade_tostringPlan slug that lifts a plan limit (plan_limit_error only).
    400 response: missing headerJSON
    {
      "error": {
        "type": "invalid_request_error",
        "code": "missing_header",
        "message": "The X-Workspace-Id header is required for session requests.",
        "param": "X-Workspace-Id",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#missing_header"
      }
    }
  • 401 response

    Missing or invalid credentials. Always code: unauthenticated; details[0].code gives the reason (missing_credentials, invalid_api_key, api_key_revoked, api_key_expired, session_expired, publishable_key_not_allowed).

    Error codes: unauthenticated

    Error schema and example
    FieldTypeDescription
    errorrequiredobject
    error.typerequiredstringOne of: invalid_request_error, authentication_error, permission_error, plan_limit_error, not_found_error, conflict_error, validation_error, rate_limit_error, api_error
    error.coderequiredstringComplete error-code catalogue. @urlcrop/shared/errors is tested against this list. param on plan_limit_error responses is always a plan limit key.One of: invalid_json, invalid_query, missing_header, invalid_cursor, invalid_sort, invalid_expand, unauthenticated, two_factor_required, step_up_required, email_verification_required, signups_paused, insufficient_scope, insufficient_role, workspace_mismatch, wrong_environment, workspace_suspended, write_blocked_past_due, content_policy_hold, origin_not_allowed, turnstile_failed, second_approver_required, view_as_read_only, support_access_approval_required, feature_not_in_plan, plan_limit_reached, resource_not_found, route_not_found, slug_taken, domain_already_exists, member_already_exists, invitation_pending, idempotency_in_progress, state_conflict, resource_in_use, payload_too_large, unsupported_media_type, validation_failed, destination_invalid, destination_blocked, destination_unreachable, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, invalid_routing_rule, invalid_currency, csv_invalid, attribution_key_missing, policy_acceptance_required, rate_limited, internal_error, upstream_unavailable, service_unavailable, timeout
    error.messagerequiredstringHuman-readable, safe to show to end users.
    error.paramstringField, header or scope the error refers to, when applicable.
    error.request_idrequiredstring
    error.doc_urlrequiredstringFormat: uri
    error.detailsarray of object
    error.details[].pathstring
    error.details[].coderequiredstringZod issue code, unsupported_in_phase, a destination safety reason, or a 401 reason.
    error.details[].messagerequiredstring
    error.details[].limitinteger | nullPlan limit in force (plan_limit_error only; null = unlimited).
    error.details[].usedintegerCurrent usage (plan_limit_error only).
    error.details[].resets_atstringWhen a periodic limit resets (plan_limit_error only).Format: date-time
    error.details[].upgrade_tostringPlan slug that lifts a plan limit (plan_limit_error only).
    401 response: unauthenticatedJSON
    {
      "error": {
        "type": "authentication_error",
        "code": "unauthenticated",
        "message": "The API key is not valid.",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#unauthenticated",
        "details": [
          {
            "path": "Authorization",
            "code": "invalid_api_key",
            "message": "No key matches this secret"
          }
        ]
      }
    }
  • 403 response

    Permitted credential but not allowed: insufficient_scope, insufficient_role, workspace_mismatch, wrong_environment, two_factor_required (also Owner/Admin writes on Business+ after the 14-day 2FA grace, details[0].grace_ends_at), step_up_required, email_verification_required, signups_paused (sign-up while the platform setting is on), write_blocked_past_due, workspace_suspended, content_policy_hold, origin_not_allowed, or a plan limit (type: plan_limit_error — feature_not_in_plan, plan_limit_reached with param naming the plan limit key). Any credentialed operation can return it: an API key on a session-only route is insufficient_scope, a session write without an allowed Origin is origin_not_allowed, and an API key whose plan has no API access is feature_not_in_plan.

    Error codes: two_factor_required, step_up_required, email_verification_required, signups_paused, insufficient_scope, insufficient_role, workspace_mismatch, wrong_environment, workspace_suspended, write_blocked_past_due, content_policy_hold, origin_not_allowed, feature_not_in_plan, plan_limit_reached

    Error schema and example
    FieldTypeDescription
    errorrequiredobject
    error.typerequiredstringOne of: invalid_request_error, authentication_error, permission_error, plan_limit_error, not_found_error, conflict_error, validation_error, rate_limit_error, api_error
    error.coderequiredstringComplete error-code catalogue. @urlcrop/shared/errors is tested against this list. param on plan_limit_error responses is always a plan limit key.One of: invalid_json, invalid_query, missing_header, invalid_cursor, invalid_sort, invalid_expand, unauthenticated, two_factor_required, step_up_required, email_verification_required, signups_paused, insufficient_scope, insufficient_role, workspace_mismatch, wrong_environment, workspace_suspended, write_blocked_past_due, content_policy_hold, origin_not_allowed, turnstile_failed, second_approver_required, view_as_read_only, support_access_approval_required, feature_not_in_plan, plan_limit_reached, resource_not_found, route_not_found, slug_taken, domain_already_exists, member_already_exists, invitation_pending, idempotency_in_progress, state_conflict, resource_in_use, payload_too_large, unsupported_media_type, validation_failed, destination_invalid, destination_blocked, destination_unreachable, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, invalid_routing_rule, invalid_currency, csv_invalid, attribution_key_missing, policy_acceptance_required, rate_limited, internal_error, upstream_unavailable, service_unavailable, timeout
    error.messagerequiredstringHuman-readable, safe to show to end users.
    error.paramstringField, header or scope the error refers to, when applicable.
    error.request_idrequiredstring
    error.doc_urlrequiredstringFormat: uri
    error.detailsarray of object
    error.details[].pathstring
    error.details[].coderequiredstringZod issue code, unsupported_in_phase, a destination safety reason, or a 401 reason.
    error.details[].messagerequiredstring
    error.details[].limitinteger | nullPlan limit in force (plan_limit_error only; null = unlimited).
    error.details[].usedintegerCurrent usage (plan_limit_error only).
    error.details[].resets_atstringWhen a periodic limit resets (plan_limit_error only).Format: date-time
    error.details[].upgrade_tostringPlan slug that lifts a plan limit (plan_limit_error only).
    403 response: plan limit reachedJSON
    {
      "error": {
        "type": "plan_limit_error",
        "code": "plan_limit_reached",
        "message": "You have created 2,000 of 2,000 links this billing period. Upgrade or buy a pack to continue.",
        "param": "links_per_month",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/pricing",
        "details": [
          {
            "code": "plan_limit_reached",
            "message": "Monthly link quota reached",
            "limit": 2000,
            "used": 2000,
            "resets_at": "2026-12-01T00:00:00.000Z",
            "upgrade_to": "business"
          }
        ]
      }
    }
    403 response: feature not in planJSON
    {
      "error": {
        "type": "plan_limit_error",
        "code": "feature_not_in_plan",
        "message": "The public API is available on the Pro plan and above.",
        "param": "api_requests_per_day",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/pricing",
        "details": [
          {
            "code": "feature_not_in_plan",
            "message": "Upgrade to Pro",
            "upgrade_to": "pro"
          }
        ]
      }
    }
    403 response: email verification requiredJSON
    {
      "error": {
        "type": "permission_error",
        "code": "email_verification_required",
        "message": "Verify your email address to create links.",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#email_verification_required"
      }
    }
    403 response: step up requiredJSON
    {
      "error": {
        "type": "permission_error",
        "code": "step_up_required",
        "message": "Confirm your password or 2FA code to continue.",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#step_up_required"
      }
    }
  • 404 response

    Unknown id, or the resource belongs to another workspace (never distinguished). Also returned when a session's X-Workspace-Id names a workspace the user is not an active member of.

    Error codes: resource_not_found

    Error schema and example
    FieldTypeDescription
    errorrequiredobject
    error.typerequiredstringOne of: invalid_request_error, authentication_error, permission_error, plan_limit_error, not_found_error, conflict_error, validation_error, rate_limit_error, api_error
    error.coderequiredstringComplete error-code catalogue. @urlcrop/shared/errors is tested against this list. param on plan_limit_error responses is always a plan limit key.One of: invalid_json, invalid_query, missing_header, invalid_cursor, invalid_sort, invalid_expand, unauthenticated, two_factor_required, step_up_required, email_verification_required, signups_paused, insufficient_scope, insufficient_role, workspace_mismatch, wrong_environment, workspace_suspended, write_blocked_past_due, content_policy_hold, origin_not_allowed, turnstile_failed, second_approver_required, view_as_read_only, support_access_approval_required, feature_not_in_plan, plan_limit_reached, resource_not_found, route_not_found, slug_taken, domain_already_exists, member_already_exists, invitation_pending, idempotency_in_progress, state_conflict, resource_in_use, payload_too_large, unsupported_media_type, validation_failed, destination_invalid, destination_blocked, destination_unreachable, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, invalid_routing_rule, invalid_currency, csv_invalid, attribution_key_missing, policy_acceptance_required, rate_limited, internal_error, upstream_unavailable, service_unavailable, timeout
    error.messagerequiredstringHuman-readable, safe to show to end users.
    error.paramstringField, header or scope the error refers to, when applicable.
    error.request_idrequiredstring
    error.doc_urlrequiredstringFormat: uri
    error.detailsarray of object
    error.details[].pathstring
    error.details[].coderequiredstringZod issue code, unsupported_in_phase, a destination safety reason, or a 401 reason.
    error.details[].messagerequiredstring
    error.details[].limitinteger | nullPlan limit in force (plan_limit_error only; null = unlimited).
    error.details[].usedintegerCurrent usage (plan_limit_error only).
    error.details[].resets_atstringWhen a periodic limit resets (plan_limit_error only).Format: date-time
    error.details[].upgrade_tostringPlan slug that lifts a plan limit (plan_limit_error only).
    404 response: not foundJSON
    {
      "error": {
        "type": "not_found_error",
        "code": "resource_not_found",
        "message": "No link with id lnk_01JB4N8ZK3Q9WXYZ0123456789.",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#resource_not_found"
      }
    }
  • 429 response

    Burst limit, daily plan quota or endpoint limit exceeded.

    Error codes: rate_limited

    Error schema and example
    FieldTypeDescription
    errorrequiredobject
    error.typerequiredstringOne of: invalid_request_error, authentication_error, permission_error, plan_limit_error, not_found_error, conflict_error, validation_error, rate_limit_error, api_error
    error.coderequiredstringComplete error-code catalogue. @urlcrop/shared/errors is tested against this list. param on plan_limit_error responses is always a plan limit key.One of: invalid_json, invalid_query, missing_header, invalid_cursor, invalid_sort, invalid_expand, unauthenticated, two_factor_required, step_up_required, email_verification_required, signups_paused, insufficient_scope, insufficient_role, workspace_mismatch, wrong_environment, workspace_suspended, write_blocked_past_due, content_policy_hold, origin_not_allowed, turnstile_failed, second_approver_required, view_as_read_only, support_access_approval_required, feature_not_in_plan, plan_limit_reached, resource_not_found, route_not_found, slug_taken, domain_already_exists, member_already_exists, invitation_pending, idempotency_in_progress, state_conflict, resource_in_use, payload_too_large, unsupported_media_type, validation_failed, destination_invalid, destination_blocked, destination_unreachable, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, invalid_routing_rule, invalid_currency, csv_invalid, attribution_key_missing, policy_acceptance_required, rate_limited, internal_error, upstream_unavailable, service_unavailable, timeout
    error.messagerequiredstringHuman-readable, safe to show to end users.
    error.paramstringField, header or scope the error refers to, when applicable.
    error.request_idrequiredstring
    error.doc_urlrequiredstringFormat: uri
    error.detailsarray of object
    error.details[].pathstring
    error.details[].coderequiredstringZod issue code, unsupported_in_phase, a destination safety reason, or a 401 reason.
    error.details[].messagerequiredstring
    error.details[].limitinteger | nullPlan limit in force (plan_limit_error only; null = unlimited).
    error.details[].usedintegerCurrent usage (plan_limit_error only).
    error.details[].resets_atstringWhen a periodic limit resets (plan_limit_error only).Format: date-time
    error.details[].upgrade_tostringPlan slug that lifts a plan limit (plan_limit_error only).
    429 response: rate limitedJSON
    {
      "error": {
        "type": "rate_limit_error",
        "code": "rate_limited",
        "message": "Daily API quota of 50,000 requests exhausted. Resets at 00:00 UTC.",
        "param": "daily_quota",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/rate-limits"
      }
    }