Skip to content

Insights

Insights endpoints of the URLCrop API: 6 operations. Analytics served from hourly/daily rollups; raw recent events; CSV exports.

Time series of one or more metrics

GET/insights/timeseries

  • Scope insights:read

Served from hourly/daily rollups. interval=hour is limited to a 7-day range. Ranges beyond the plan's retention are truncated (truncated_to_retention: true).

Parameters

NameInTypeDescription
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.
metricsrequiredquerystringComma-separated. conversions and revenue are Phase 2.
intervalquerystringOne of: hour, day, week, month
fromquerystringStart of the period (inclusive). YYYY-MM-DD or RFC 3339. Default 30 days ago.
toquerystringEnd of the period (inclusive). Default now.
timezonequerystringIANA timezone for bucketing. Default is the workspace timezone.
include_botsquerybooleanWhen true, flagged bot events are added into click, scan and view counts (the rollups' bots column). Default false excludes them. unique_visitors never includes bots.
comparequerystringprevious adds the previous period of equal length (a whole calendar month compares with the previous calendar month). timeseries returns a previous series and per-metric deltas; top-links returns a delta per row.Value: previous
link_idquerystring
qr_code_idquerystring
page_idquerystring
campaign_idquerystring
domainquerystring
countryquerystringServed from the country dimension rows of the rollups; one of country / device at a time (both → 422 unsupported_in_phase), combinable with one scope filter. A malformed code → 400 invalid_query. With interval=hour it reads the raw events of the last 7 days and the response is marked truncated_to_retention: true when from is cut.
devicequerystringServed from the device_type dimension rows of the rollups; one of country / device at a time (both → 422 unsupported_in_phase), combinable with one scope filter. With interval=hour it reads the raw events of the last 7 days and the response is marked truncated_to_retention: true when from is cut.One of: mobile, desktop, tablet

Request example

GET /insights/timeseriescURL
curl 'https://api.urlcrop.com/v1/insights/timeseries?metrics=clicks%2Cscans%2Cunique_visitors' \
  -H 'Authorization: Bearer uc_live_…'

Responses

  • 200 response

    Time series

    InsightsTimeseries schema and example
    FieldTypeDescription
    objectrequiredstringValue: timeseries
    intervalrequiredstringOne of: hour, day, week, month
    fromrequiredstringISO 8601 UTC with milliseconds.Format: date-time
    torequiredstringISO 8601 UTC with milliseconds.Format: date-time
    timezonerequiredstring
    metricsrequiredarray of string
    truncated_to_retentionbooleanTrue when from was moved forward to the plan's retention window.
    over_quota_eventsintegerOver-quota events (counted without detail) in the UTC months overlapping the range; not part of data.
    previousobjectOnly with compare=previous. Same buckets as data, shifted to the previous period (index-aligned).
    previous.fromrequiredstringISO 8601 UTC with milliseconds.Format: date-time
    previous.torequiredstringISO 8601 UTC with milliseconds.Format: date-time
    previous.datarequiredarray of InsightsTimeseriesPoint
    previous.data[].timestamprequiredstringISO 8601 UTC with milliseconds.Format: date-time
    previous.data[].clicksinteger
    previous.data[].scansinteger
    previous.data[].viewsinteger
    previous.data[].unique_visitorsinteger
    previous.data[].conversionsinteger
    previous.data[].revenueintegerMinor units in the workspace currency (Phase 2).
    deltasobjectOnly with compare=previous. One entry per requested metric.
    datarequiredarray of InsightsTimeseriesPoint
    data[].timestamprequiredstringISO 8601 UTC with milliseconds.Format: date-time
    data[].clicksinteger
    data[].scansinteger
    data[].viewsinteger
    data[].unique_visitorsinteger
    data[].conversionsinteger
    data[].revenueintegerMinor units in the workspace currency (Phase 2).
    200 response: dailyJSON
    {
      "object": "timeseries",
      "interval": "day",
      "from": "2026-12-01T00:00:00.000Z",
      "to": "2026-12-03T23:59:59.999Z",
      "timezone": "Asia/Kolkata",
      "metrics": [
        "clicks",
        "scans"
      ],
      "truncated_to_retention": false,
      "data": [
        {
          "timestamp": "2026-11-30T18:30:00.000Z",
          "clicks": 1200,
          "scans": 140
        },
        {
          "timestamp": "2026-12-01T18:30:00.000Z",
          "clicks": 1380,
          "scans": 162
        },
        {
          "timestamp": "2026-12-02T18:30:00.000Z",
          "clicks": 990,
          "scans": 121
        }
      ]
    }
  • 304 response

    The If-None-Match ETag matches; no body. Sent for cacheable aggregate reads (Cache-Control: private, max-age=30, weak ETag).

  • 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"
      }
    }
  • 422 response

    Validation failure (validation_failed, destination_invalid, destination_blocked (details[0].code is policy_violation, with the category, for content-policy blocks), slug_invalid, slug_reserved, slug_not_allowed, handle_not_allowed, title_not_allowed, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, policy_acceptance_required, …).

    Error codes: validation_failed, destination_invalid, destination_blocked, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, policy_acceptance_required

    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).
    422 response: validation failedJSON
    {
      "error": {
        "type": "validation_error",
        "code": "validation_failed",
        "message": "The request body is invalid.",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#validation_failed",
        "details": [
          {
            "path": "destination_url",
            "code": "invalid_string",
            "message": "Must be an absolute http(s) URL"
          }
        ]
      }
    }
    422 response: destination blockedJSON
    {
      "error": {
        "type": "validation_error",
        "code": "destination_blocked",
        "message": "This destination was flagged as unsafe and cannot be shortened.",
        "param": "destination_url",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#destination_blocked"
      }
    }
  • 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"
      }
    }

Break one metric down by a dimension

GET/insights/breakdown

  • Scope insights:read

Parameters

NameInTypeDescription
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.
dimensionrequiredquerystringhour_of_day (keys 0..23) and day_of_week (keys mon..sun) are bucketed in the request timezone, always return every slot (zeros included) and ignore limit. page and campaign rows use the public id as key and the title / name as label.One of: country, city, device, os, browser, referrer, language, link, qr_code, page, campaign, domain, utm_source, utm_medium, utm_campaign, hour_of_day, day_of_week
metricrequiredquerystringOne of: clicks, scans, views, unique_visitors, conversions, revenue
fromquerystringStart of the period (inclusive). YYYY-MM-DD or RFC 3339. Default 30 days ago.
toquerystringEnd of the period (inclusive). Default now.
timezonequerystringIANA timezone for bucketing. Default is the workspace timezone.
include_botsquerybooleanWhen true, flagged bot events are added into click, scan and view counts (the rollups' bots column). Default false excludes them. unique_visitors never includes bots.
link_idquerystring
qr_code_idquerystringServed from qr rollups; alone only (with link_id, campaign_id, domain or page_id → 422 unsupported_in_phase).
page_idquerystringServed from page rollups; alone only (with link_id, qr_code_id, campaign_id or domain → 422 unsupported_in_phase).
campaign_idquerystring
domainquerystring
countryquerystringISO 3166-1 alpha-2 code. Combines with the attribute dimensions (city, device, os, browser, referrer, language, utm_*) and with the other scope filters. For city it selects the rollup rows of that country (CC|City keys) for any range (no truncation); for the other attribute dimensions rollup rows carry one dimension each, so it reads the raw events of the last 7 days (from is moved forward to that window and the response is marked truncated_to_retention: true). link, domain, qr_code, page, campaign, hour_of_day and day_of_week with country → 422 unsupported_in_phase. A malformed code → 400 invalid_query.
devicequerystringNot served for breakdowns yet (422 unsupported_in_phase).One of: mobile, desktop, tablet
limitqueryinteger

Request example

GET /insights/breakdowncURL
curl 'https://api.urlcrop.com/v1/insights/breakdown?dimension=country&metric=clicks' \
  -H 'Authorization: Bearer uc_live_…'

Responses

  • 200 response

    Breakdown

    InsightsBreakdown schema and example
    FieldTypeDescription
    objectrequiredstringValue: breakdown
    dimensionrequiredstringhour_of_day (keys 0..23) and day_of_week (keys mon..sun) are bucketed in the request timezone, always return every slot (zeros included) and ignore limit. page and campaign rows use the public id as key and the title / name as label.One of: country, city, device, os, browser, referrer, language, link, qr_code, page, campaign, domain, utm_source, utm_medium, utm_campaign, hour_of_day, day_of_week
    metricrequiredstringOne of: clicks, scans, views, unique_visitors, conversions, revenue
    fromrequiredstringISO 8601 UTC with milliseconds.Format: date-time
    torequiredstringISO 8601 UTC with milliseconds.Format: date-time
    totalrequirednumber
    truncated_to_retentionboolean
    datarequiredarray of BreakdownRow
    data[].keyrequiredstringDimension value (ISO country code, device class, referrer host, link id…).
    data[].labelstring
    data[].valuerequirednumber
    data[].sharenumberFraction of the total (0–1).
    200 response: countryJSON
    {
      "object": "breakdown",
      "dimension": "country",
      "metric": "clicks",
      "from": "2026-11-05T00:00:00.000Z",
      "to": "2026-12-05T23:59:59.999Z",
      "total": 13620,
      "truncated_to_retention": false,
      "data": [
        {
          "key": "IN",
          "label": "India",
          "value": 11170,
          "share": 0.82
        },
        {
          "key": "AE",
          "label": "United Arab Emirates",
          "value": 1090,
          "share": 0.08
        }
      ]
    }
  • 304 response

    The If-None-Match ETag matches; no body. Sent for cacheable aggregate reads (Cache-Control: private, max-age=30, weak ETag).

  • 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"
      }
    }
  • 422 response

    Validation failure (validation_failed, destination_invalid, destination_blocked (details[0].code is policy_violation, with the category, for content-policy blocks), slug_invalid, slug_reserved, slug_not_allowed, handle_not_allowed, title_not_allowed, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, policy_acceptance_required, …).

    Error codes: validation_failed, destination_invalid, destination_blocked, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, policy_acceptance_required

    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).
    422 response: validation failedJSON
    {
      "error": {
        "type": "validation_error",
        "code": "validation_failed",
        "message": "The request body is invalid.",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#validation_failed",
        "details": [
          {
            "path": "destination_url",
            "code": "invalid_string",
            "message": "Must be an absolute http(s) URL"
          }
        ]
      }
    }
    422 response: destination blockedJSON
    {
      "error": {
        "type": "validation_error",
        "code": "destination_blocked",
        "message": "This destination was flagged as unsafe and cannot be shortened.",
        "param": "destination_url",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#destination_blocked"
      }
    }
  • 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"
      }
    }

GET/insights/top-links

  • Scope insights:read

Same range, time-zone and filter semantics as timeseries. With compare=previous every row carries a delta.

Parameters

NameInTypeDescription
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.
metricquerystringOne of: clicks, scans, views, unique_visitors, conversions, revenue
fromquerystringStart of the period (inclusive). YYYY-MM-DD or RFC 3339. Default 30 days ago.
toquerystringEnd of the period (inclusive). Default now.
timezonequerystringIANA timezone for bucketing. Default is the workspace timezone.
include_botsquerybooleanWhen true, flagged bot events are added into click, scan and view counts (the rollups' bots column). Default false excludes them. unique_visitors never includes bots.
comparequerystringprevious adds the previous period of equal length (a whole calendar month compares with the previous calendar month). timeseries returns a previous series and per-metric deltas; top-links returns a delta per row.Value: previous
link_idquerystring
qr_code_idquerystring
page_idquerystring
campaign_idquerystring
domainquerystring
countryquerystringServed from the country dimension rows of the rollups; one of country / device at a time (both → 422 unsupported_in_phase), combinable with one scope filter. A malformed code → 400 invalid_query. With interval=hour it reads the raw events of the last 7 days and the response is marked truncated_to_retention: true when from is cut.
devicequerystringServed from the device_type dimension rows of the rollups; one of country / device at a time (both → 422 unsupported_in_phase), combinable with one scope filter. With interval=hour it reads the raw events of the last 7 days and the response is marked truncated_to_retention: true when from is cut.One of: mobile, desktop, tablet
limitqueryinteger

Request example

GET /insights/top-linkscURL
curl 'https://api.urlcrop.com/v1/insights/top-links' \
  -H 'Authorization: Bearer uc_live_…'

Responses

  • Top links

    InsightsTopLinks schema and example
    FieldTypeDescription
    objectrequiredstringValue: top_links
    metricrequiredstringOne of: clicks, scans, views, unique_visitors, conversions, revenue
    fromrequiredstringISO 8601 UTC with milliseconds.Format: date-time
    torequiredstringISO 8601 UTC with milliseconds.Format: date-time
    truncated_to_retentionboolean
    previousobjectOnly with compare=previous.
    previous.fromrequiredstringISO 8601 UTC with milliseconds.Format: date-time
    previous.torequiredstringISO 8601 UTC with milliseconds.Format: date-time
    datarequiredarray of object
    data[].link_idrequiredstring
    data[].short_urlrequiredstringFormat: uri
    data[].titlestring | null
    data[].valuerequirednumber
    data[].deltaInsightsDeltaOnly with compare=previous.
    data[].delta.valuerequirednumber
    data[].delta.previousrequirednumber
    data[].delta.delta_pctrequirednumber | null
    200 response: 200 responseJSON
    {
      "object": "top_links",
      "metric": "clicks",
      "from": "2026-12-05T10:15:30.000Z",
      "to": "2026-12-05T10:15:30.000Z",
      "truncated_to_retention": false,
      "previous": {
        "from": "2026-12-05T10:15:30.000Z",
        "to": "2026-12-05T10:15:30.000Z"
      },
      "data": [
        {
          "link_id": "string",
          "short_url": "https://example.com",
          "title": "string",
          "value": 0,
          "delta": {
            "value": 0,
            "previous": 0,
            "delta_pct": 0
          }
        }
      ]
    }
    Example generated from the schema.
  • The If-None-Match ETag matches; no body. Sent for cacheable aggregate reads (Cache-Control: private, max-age=30, weak ETag).

  • 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"
      }
    }
  • 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"
          }
        ]
      }
    }
  • 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"
      }
    }
  • 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"
      }
    }
  • Validation failure (validation_failed, destination_invalid, destination_blocked (details[0].code is policy_violation, with the category, for content-policy blocks), slug_invalid, slug_reserved, slug_not_allowed, handle_not_allowed, title_not_allowed, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, policy_acceptance_required, …).

    Error codes: validation_failed, destination_invalid, destination_blocked, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, policy_acceptance_required

    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).
    422 response: validation failedJSON
    {
      "error": {
        "type": "validation_error",
        "code": "validation_failed",
        "message": "The request body is invalid.",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#validation_failed",
        "details": [
          {
            "path": "destination_url",
            "code": "invalid_string",
            "message": "Must be an absolute http(s) URL"
          }
        ]
      }
    }
    422 response: destination blockedJSON
    {
      "error": {
        "type": "validation_error",
        "code": "destination_blocked",
        "message": "This destination was flagged as unsafe and cannot be shortened.",
        "param": "destination_url",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#destination_blocked"
      }
    }
  • 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"
      }
    }

Raw recent events (last 7 days, ≤ 10,000 rows per query)

GET/insights/events

  • Scope insights:read

Reads the partitioned events table directly; from is clamped to the last 7 days, a query returns at most 10,000 rows across its pages, and the dashboard's live strip polls it every 10 s. Use exports for anything older or larger. Visitor hashes are never returned.

Parameters

NameInTypeDescription
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.
cursorquerystringOpaque cursor from pagination.next_cursor.
limitqueryinteger
typequerystringOne of: click, scan, view, conversion
click_idquerystring
include_botsqueryboolean
sincequerystringRFC 3339 instant, at most 7 days old; only events at or after it are returned (the live strip asks for the last 60 s).
fromquerystringStart of the period (inclusive). YYYY-MM-DD or RFC 3339. Default 30 days ago.
toquerystringEnd of the period (inclusive). Default now.
timezonequerystringIANA timezone for bucketing. Default is the workspace timezone.
link_idquerystring
qr_code_idquerystring
page_idquerystring
campaign_idquerystring
domainquerystring
countryquerystring
devicequerystringOne of: mobile, desktop, tablet

Request example

GET /insights/eventscURL
curl 'https://api.urlcrop.com/v1/insights/events' \
  -H 'Authorization: Bearer uc_live_…'

Responses

  • 200 response

    Events, newest first

    InsightsEventList schema and example
    FieldTypeDescription
    datarequiredarray of InsightsEvent
    data[].event_typerequiredstringOne of: click, scan, view, conversion
    data[].occurred_atrequiredstringISO 8601 UTC with milliseconds.Format: date-time
    data[].link_idstring | null
    data[].qr_code_idstring | null
    data[].page_idstring | null
    data[].campaign_idstring | null
    data[].countrystring | null
    data[].regionstring | null
    data[].citystring | null
    data[].devicestring | null
    data[].osstring | null
    data[].browserstring | null
    data[].referrer_domainstring | null
    data[].referrer_classstring | nullOne of: direct, search, social, email, referral, other, null
    data[].is_botrequiredboolean
    data[].click_idstring | null
    paginationrequiredPagination
    pagination.limitrequiredinteger
    pagination.has_morerequiredboolean
    pagination.next_cursorrequiredstring | nullPass as cursor to fetch the next page. null on the last page.
    pagination.totalintegerOnly with include_total=true; estimated above 10,000.
    200 response: 200 responseJSON
    {
      "data": [
        {
          "event_type": "click",
          "occurred_at": "2026-12-05T10:15:30.000Z",
          "link_id": "string",
          "qr_code_id": "string",
          "page_id": "string",
          "campaign_id": "string",
          "country": "string",
          "region": "string",
          "city": "string",
          "device": "string",
          "os": "string",
          "browser": "string",
          "referrer_domain": "string",
          "referrer_class": "direct",
          "is_bot": false,
          "click_id": "clk_01JB59XQ0P1N2M3L4K5J6H7G8F"
        }
      ],
      "pagination": {
        "limit": 0,
        "has_more": false,
        "next_cursor": "string",
        "total": 0
      }
    }
    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"
      }
    }
  • 422 response

    Validation failure (validation_failed, destination_invalid, destination_blocked (details[0].code is policy_violation, with the category, for content-policy blocks), slug_invalid, slug_reserved, slug_not_allowed, handle_not_allowed, title_not_allowed, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, policy_acceptance_required, …).

    Error codes: validation_failed, destination_invalid, destination_blocked, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, policy_acceptance_required

    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).
    422 response: validation failedJSON
    {
      "error": {
        "type": "validation_error",
        "code": "validation_failed",
        "message": "The request body is invalid.",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#validation_failed",
        "details": [
          {
            "path": "destination_url",
            "code": "invalid_string",
            "message": "Must be an absolute http(s) URL"
          }
        ]
      }
    }
    422 response: destination blockedJSON
    {
      "error": {
        "type": "validation_error",
        "code": "destination_blocked",
        "message": "This destination was flagged as unsafe and cannot be shortened.",
        "param": "destination_url",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#destination_blocked"
      }
    }
  • 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"
      }
    }

Start a CSV export (async, 10/hour, ≤ 5 M rows)

POST/insights/exports

  • Scope insights:read

Runs in jobs against raw events within the plan's retention (scope: events) or the workspace series (scope: workspace, daily rows from the rollups; filters.device or filters.country select that dimension's rollup rows, one at a time, both together → 422 unsupported_in_phase; filters.type stays unsupported there). The file is emailed to the requester and listed in the app; the signed URL is valid 24 h. Poll GET /insights/exports/{id}.

Parameters

NameInTypeDescription
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.
Idempotency-KeyheaderstringUnique string (UUID v4 recommended, ≤ 255 chars). Repeating a POST with the same key and body within 24 hours replays the original response with Idempotent-Replayed: true. One-time secrets are never cached: a replay returns them as null with Idempotent-Redacted: secret.

Request body application/json

FieldTypeDescription
formatrequiredstringValue: csv
scopestringOne of: events, workspace
fromrequiredstringYYYY-MM-DD or RFC 3339.
torequiredstring
timezonestring
filtersobject
filters.link_idstring
filters.qr_code_idstring
filters.page_idstring
filters.campaign_idstring
filters.domainstring
filters.countrystring
filters.devicestringOne of: mobile, desktop, tablet
filters.typestringOne of: click, scan, view, conversion
filters.include_botsboolean
columnsarray of stringSubset of the scope's columns, in output order. Column lists per scope are fixed by the contract; hashes are never exportable.
Request body: campaign clicksJSON
{
  "format": "csv",
  "scope": "events",
  "from": "2026-11-01",
  "to": "2026-11-30",
  "timezone": "Asia/Kolkata",
  "filters": {
    "campaign_id": "cmp_01JB4N0DIWALI2026XXXXXXXXX",
    "type": "click"
  }
}

Request example

POST /insights/exportscURL
curl -X POST 'https://api.urlcrop.com/v1/insights/exports' \
  -H 'Authorization: Bearer uc_live_…' \
  -H 'Content-Type: application/json' \
  -d '{
  "format": "csv",
  "scope": "events",
  "from": "2026-11-01",
  "to": "2026-11-30",
  "timezone": "Asia/Kolkata",
  "filters": {
    "campaign_id": "cmp_01JB4N0DIWALI2026XXXXXXXXX",
    "type": "click"
  }
}'

Responses

  • 202 response

    Queued (status: queued)

    InsightsExport schema and example
    FieldTypeDescription
    idrequiredstring
    objectrequiredstringValue: export
    statusrequiredstringexpired once expires_at has passed (the file is deleted within the hour); no download_url after that.One of: queued, running, done, failed, expired
    formatstringValue: csv
    scoperequiredstringOne of: events, workspace
    fromstring
    tostring
    rowsinteger | null
    bytesinteger | null
    download_urlstring | nullSigned file URL, valid 24 h; only when status is done.Format: uri
    expires_atstring | nullFormat: date-time
    error_codestring | null
    truncatedbooleanTrue when the export stopped at the 5,000,000-row cap; the file holds the first rows of the range.
    created_atrequiredstringISO 8601 UTC with milliseconds.Format: date-time
    completed_atstring | nullFormat: date-time
    202 response: 202 responseJSON
    {
      "id": "exp_01JB4PF6G7H8J9K0L1M2N3P4Q5",
      "object": "export",
      "status": "queued",
      "format": "csv",
      "scope": "events",
      "from": "string",
      "to": "string",
      "rows": 0,
      "bytes": 0,
      "download_url": "https://example.com",
      "expires_at": "2026-12-05T10:15:30.000Z",
      "error_code": "string",
      "truncated": false,
      "created_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"
      }
    }
  • 409 response

    State conflict (slug_taken, domain_already_exists, member_already_exists, invitation_pending, idempotency_in_progress, state_conflict, resource_in_use).

    Error codes: slug_taken, domain_already_exists, member_already_exists, invitation_pending, idempotency_in_progress, state_conflict, resource_in_use

    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).
    409 response: slug takenJSON
    {
      "error": {
        "type": "conflict_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"
      }
    }
  • 422 response

    Validation failure (validation_failed, destination_invalid, destination_blocked (details[0].code is policy_violation, with the category, for content-policy blocks), slug_invalid, slug_reserved, slug_not_allowed, handle_not_allowed, title_not_allowed, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, policy_acceptance_required, …).

    Error codes: validation_failed, destination_invalid, destination_blocked, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, policy_acceptance_required

    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).
    422 response: validation failedJSON
    {
      "error": {
        "type": "validation_error",
        "code": "validation_failed",
        "message": "The request body is invalid.",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#validation_failed",
        "details": [
          {
            "path": "destination_url",
            "code": "invalid_string",
            "message": "Must be an absolute http(s) URL"
          }
        ]
      }
    }
    422 response: destination blockedJSON
    {
      "error": {
        "type": "validation_error",
        "code": "destination_blocked",
        "message": "This destination was flagged as unsafe and cannot be shortened.",
        "param": "destination_url",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#destination_blocked"
      }
    }
  • 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"
      }
    }

Export status and download URL

GET/insights/exports/{id}

  • Scope insights:read

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 /insights/exports/{id}cURL
curl 'https://api.urlcrop.com/v1/insights/exports/lnk_01JB4N8ZK3Q9WXYZ0123456789' \
  -H 'Authorization: Bearer uc_live_…'

Responses

  • 200 response

    Export

    InsightsExport schema and example
    FieldTypeDescription
    idrequiredstring
    objectrequiredstringValue: export
    statusrequiredstringexpired once expires_at has passed (the file is deleted within the hour); no download_url after that.One of: queued, running, done, failed, expired
    formatstringValue: csv
    scoperequiredstringOne of: events, workspace
    fromstring
    tostring
    rowsinteger | null
    bytesinteger | null
    download_urlstring | nullSigned file URL, valid 24 h; only when status is done.Format: uri
    expires_atstring | nullFormat: date-time
    error_codestring | null
    truncatedbooleanTrue when the export stopped at the 5,000,000-row cap; the file holds the first rows of the range.
    created_atrequiredstringISO 8601 UTC with milliseconds.Format: date-time
    completed_atstring | nullFormat: date-time
    200 response: 200 responseJSON
    {
      "id": "exp_01JB4PF6G7H8J9K0L1M2N3P4Q5",
      "object": "export",
      "status": "queued",
      "format": "csv",
      "scope": "events",
      "from": "string",
      "to": "string",
      "rows": 0,
      "bytes": 0,
      "download_url": "https://example.com",
      "expires_at": "2026-12-05T10:15:30.000Z",
      "error_code": "string",
      "truncated": false,
      "created_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"
      }
    }
  • 422 response

    Validation failure (validation_failed, destination_invalid, destination_blocked (details[0].code is policy_violation, with the category, for content-policy blocks), slug_invalid, slug_reserved, slug_not_allowed, handle_not_allowed, title_not_allowed, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, policy_acceptance_required, …).

    Error codes: validation_failed, destination_invalid, destination_blocked, slug_invalid, slug_reserved, domain_not_active, domain_proxied, idempotency_key_reused, unsupported_qr_type, qr_unreadable, policy_acceptance_required

    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).
    422 response: validation failedJSON
    {
      "error": {
        "type": "validation_error",
        "code": "validation_failed",
        "message": "The request body is invalid.",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#validation_failed",
        "details": [
          {
            "path": "destination_url",
            "code": "invalid_string",
            "message": "Must be an absolute http(s) URL"
          }
        ]
      }
    }
    422 response: destination blockedJSON
    {
      "error": {
        "type": "validation_error",
        "code": "destination_blocked",
        "message": "This destination was flagged as unsafe and cannot be shortened.",
        "param": "destination_url",
        "request_id": "req_01JB4N8ZK3Q9WXYZ0123456789",
        "doc_url": "https://urlcrop.com/docs/api/errors#destination_blocked"
      }
    }
  • 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"
      }
    }