Skip to content

Collect

Collect endpoints of the URLCrop API: 1 operation. Browser-snippet endpoint authenticated with a publishable key (Phase 2).

Browser snippet endpoint (publishable key)

POST/collect

  • Publishable key
  • business plan and above
  • Coming soon

Coming soon: this endpoint is documented ahead of its release and is not available yet.

Phase 2. Authenticated with Authorization: Bearer uc_pub_…; the request Origin must be in the key's allowed_origins. type: init returns the snippet configuration; type: conversion records a Conversion with source: snippet; type: identify attaches hashed customer identifiers to the current click_id. 600 requests/min per key, body ≤ 16 KB.

Parameters

NameInTypeDescription
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
typerequiredstringOne of: init, conversion, identify
click_idstring | null
page_urlrequiredstringFormat: uri
tsrequiredintegerClient Unix time in ms; rejected if more than 10 minutes off.
eventstringOne of: lead, signup, add_to_cart, purchase, custom
order_idstring
external_idstring
valueMoney
value.amountrequiredintegerInteger minor units (paise, cents). Never a float.
value.currencyrequiredstringISO 4217 code.One of: INR, USD
coupon_codestring
customerConversionCustomerHashed identifiers only. Lower-case and trim before hashing with SHA-256 (hex).
customer.external_idstringmax length 128
customer.email_hashstring
customer.phone_hashstringSHA-256 of the E.164 number.
metadataMetadataUp to 20 keys (≤ 40 chars) with string values (≤ 500 chars). Stored verbatim.max keys 20
Request body: initJSON
{
  "type": "init",
  "click_id": "clk_01JB59XQ0P1N2M3L4K5J6H7G8F",
  "page_url": "https://shop.brand.in/diwali-sale?ucid=clk_01JB59XQ0P1N2M3L4K5J6H7G8F",
  "ts": 1771059718000
}
Request body: purchaseJSON
{
  "type": "conversion",
  "event": "purchase",
  "click_id": "clk_01JB59XQ0P1N2M3L4K5J6H7G8F",
  "order_id": "SHOP-10045",
  "value": {
    "amount": 249900,
    "currency": "INR"
  },
  "page_url": "https://shop.brand.in/thank-you",
  "ts": 1771059911000
}

Request example

POST /collectcURL
curl -X POST 'https://api.urlcrop.com/v1/collect' \
  -H 'Authorization: Bearer uc_pub_…' \
  -H 'Content-Type: application/json' \
  -d '{
  "type": "init",
  "click_id": "clk_01JB59XQ0P1N2M3L4K5J6H7G8F",
  "page_url": "https://shop.brand.in/diwali-sale?ucid=clk_01JB59XQ0P1N2M3L4K5J6H7G8F",
  "ts": 1771059718000
}'

Responses

  • 200 response

    Snippet configuration (type: init)

    CollectConfig schema and example
    FieldTypeDescription
    configrequiredobject
    config.cookie_daysrequiredinteger
    config.click_id_paramrequiredstring
    config.auto_capturerequiredboolean
    config.pixelsrequiredarray of object
    config.pixels[].providerrequiredstringOne of: meta, google_ads, linkedin, tiktok, x
    config.pixels[].pixel_idrequiredstring
    200 response: 200 responseJSON
    {
      "config": {
        "cookie_days": 90,
        "click_id_param": "ucid",
        "auto_capture": false,
        "pixels": [
          {
            "provider": "meta",
            "pixel_id": "string"
          }
        ]
      }
    }
    Example generated from the schema.
  • 202 response

    Conversion queued (type: conversion)

    ConversionAccepted schema and example
    FieldTypeDescription
    idrequiredstring
    objectrequiredstringValue: conversion
    statusrequiredstringOne of: queued, duplicate
    duplicate_ofstring | null
    202 response: 202 responseJSON
    {
      "id": "string",
      "object": "conversion",
      "status": "queued",
      "duplicate_of": "string"
    }
    Example generated from the schema.
  • 204 response

    Identify applied (type: identify)

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