Customers

A customer is a merchant that installed your app — one Installation. Two endpoints: GET /api/v1/apps/:app/customers returns a paginated object: "list" of lean merchant rows (identity, plan, status, MRR, install dates, tags), and GET /api/v1/apps/:app/customers/:customer returns a single merchant's full profile — contacts, custom fields, plan and subscription, revenue, health score, tags, shop admin data, uninstall reasons, and their App Store review. Both require the crm:read scope.


GET/api/v1/apps/:app/customers

List customers

Returns the app's merchants as a true-paginated object: "list", newest install first by default. total_count is the count after filters and has_more tells you when to fetch the next page. The envelope's metadata.summary is computed over the filtered set (total_customers, active, paying, on_trial), and metadata.filters echoes back the parameters that were applied.

Rows are lean by design — identity, plan, status, MRR, dates, and tags. For a merchant's contacts, custom fields, revenue, and review, retrieve the single customer below.

Path parameter

  • Name
    app
    Type
    string
    Description

    The team app's ULID, e.g. 01JN4HKQX0000000000000000.

Query parameters

  • Name
    status
    Type
    string
    Description

    Filter by install status: active, uninstalled, frozen, closed, or all. Any other value returns a 422.

  • Name
    plan
    Type
    string
    Description

    Filter by current plan name (exact match on the current subscription's name).

  • Name
    tag
    Type
    string
    Description

    Filter to merchants carrying this CRM tag (exact match on tag name).

  • Name
    country
    Type
    string
    Description

    Two-letter country code of the merchant's shop, e.g. US. Case-insensitive.

  • Name
    include_test
    Type
    boolean
    Description

    Include test / development-store merchants. Defaults to false (they are excluded). Accepts true/false/1/0/yes/no.

  • Name
    installed_after
    Type
    string
    Description

    YYYY-MM-DD. Only merchants installed on or after this date.

  • Name
    installed_before
    Type
    string
    Description

    YYYY-MM-DD. Only merchants installed on or before this date. Must be greater than or equal to installed_after.

  • Name
    sort
    Type
    string
    Description

    Sort column: installed_at (default), last_seen_at, or mrr. Any other value returns a 422.

  • Name
    order
    Type
    string
    Description

    Sort direction: desc (default) or asc.

  • Name
    page
    Type
    integer
    Description

    The page to return. Default 1.

  • Name
    per_page
    Type
    integer
    Description

    Merchants per page. Default 25, maximum 100.

  • Name
    cursor
    Type
    string
    Description

    Opt-in cursor pagination (opaque, max 64 chars). When set, the response returns a next_cursor and reports page/total_count as null. Recommended for exporting the full list — see Pagination.

Request

GET
/api/v1/apps/:app/customers
curl -G https://ranksyapp.com/api/v1/apps/01JN4HKQX0000000000000000/customers \
  -H "Authorization: Bearer rk_live_..." \
  -d status=active \
  -d sort=mrr \
  -d order=desc \
  -d per_page=25

Response

{
  "object": "list",
  "url": "/api/v1/apps/01JN4HKQX0000000000000000/customers",
  "page": 1,
  "per_page": 25,
  "total_count": 342,
  "has_more": true,
  "metadata": {
    "summary": {
      "total_customers": 342,
      "active": 318,
      "paying": 96,
      "on_trial": 12
    },
    "filters": {
      "status": "active",
      "plan": null,
      "tag": null,
      "country": null,
      "include_test": false,
      "installed_after": null,
      "installed_before": null,
      "sort": "mrr",
      "order": "desc"
    }
  },
  "data": [
    {
      "id": 528913,
      "shop_id": 5271523,
      "name": "Acme Store",
      "myshopify_domain": "acme-store.myshopify.com",
      "email": "[email protected]",
      "country": "US",
      "install_status": "active",
      "is_test": false,
      "is_on_trial": false,
      "plan_name": "Growth",
      "subscription_status": "active",
      "billing_interval": "EVERY_30_DAYS",
      "current_mrr": 49.00,
      "installed_at": "2026-05-14T09:15:00+00:00",
      "last_seen_at": "2026-08-09T11:02:00+00:00",
      "trial_ends_at": null,
      "tags": [
        { "name": "vip", "color": "#7C3AED" }
      ]
    }
  ]
}

GET/api/v1/apps/:app/customers/:customer

Retrieve a customer

Returns a single merchant's full profile — everything the CRM holds for that shop. The :customer id is the numeric id from a list row. A merchant that doesn't belong to :app (including one on another of your apps) returns 404 — a key can only read its own app's merchants.

Path parameters

  • Name
    app
    Type
    string
    Description

    The team app's ULID, e.g. 01JN4HKQX0000000000000000.

  • Name
    customer
    Type
    integer
    Description

    The merchant's numeric id (the id field from a list row).

Request

GET
/api/v1/apps/:app/customers/:customer
curl https://ranksyapp.com/api/v1/apps/01JN4HKQX0000000000000000/customers/528913 \
  -H "Authorization: Bearer rk_live_..."

Response

{
  "data": {
    "id": 528913,
    "shop_id": 5271523,
    "name": "Acme Store",
    "myshopify_domain": "acme-store.myshopify.com",
    "email": "[email protected]",
    "country": "US",
    "install_status": "active",
    "is_test": false,
    "is_on_trial": false,
    "trial_ends_at": null,
    "first_installed_at": "2026-05-14T09:15:00+00:00",
    "last_uninstalled_at": "2026-08-12T16:40:00+00:00",
    "uninstall_reasons": [
      {
        "uninstalled_at": "2026-08-12T16:40:00+00:00",
        "reasons": [
          { "code": "not_using_now", "label": "Not using the app right now" },
          { "code": "hard_to_set_up", "label": "Hard to set up or use" }
        ],
        "detail": "Too many steps in onboarding"
      },
      {
        "uninstalled_at": "2026-06-30T08:05:00+00:00",
        "reasons": [
          { "code": "not_using_now", "label": "Not using the app right now" }
        ],
        "detail": null
      }
    ],
    "last_seen_at": "2026-08-09T11:02:00+00:00",
    "health_score": 82.5,
    "plan": {
      "name": "Growth",
      "subscription_status": "active",
      "billing_interval": "EVERY_30_DAYS",
      "amount": 49.00,
      "current_mrr": 49.00,
      "activated_at": "2026-05-21T09:15:00+00:00",
      "cancelled_at": null,
      "current_period_end": "2026-08-21T09:15:00+00:00"
    },
    "revenue": {
      "currency": "USD",
      "lifetime_value": 441.00,
      "total_revenue": 441.00,
      "usage_revenue": 0.00,
      "transaction_count": 9
    },
    "custom_fields": {
      "plan_tier": "gold",
      "seats": 5
    },
    "tags": [
      { "name": "vip", "color": "#7C3AED" }
    ],
    "contacts": [
      {
        "name": "Jane Merchant",
        "email": "[email protected]",
        "phone": "+15551234567",
        "label": "Owner"
      }
    ],
    "shop_admin_data": {
      "plan_display_name": "Shopify",
      "is_shopify_plus": false,
      "is_partner_dev_store": false,
      "shop_owner_name": "Jane Merchant",
      "primary_domain_url": "https://acme-store.com",
      "currency_code": "USD",
      "iana_timezone": "America/New_York",
      "country_code": "US",
      "city": "Brooklyn",
      "province": "New York",
      "zip": "11201",
      "phone": "+15551234567"
    },
    "review": {
      "id": 123456789,
      "rating": 5,
      "sentiment": "positive",
      "date": "2026-06-02",
      "body": "Works exactly as described and support is fast.",
      "has_reply": true,
      "reply": "Thanks for the kind words!",
      "reply_date": "2026-06-03"
    }
  }
}

Response shapes

The list is the shared catalog object: "list" envelope; each item in data is a customer row. The retrieve endpoint returns a single customer object under data — a superset of the row with the nested plan, revenue, contacts, custom_fields, shop_admin_data, uninstall_reasons, and review blocks.

List envelope (object: "list")

  • Name
    object
    Type
    string
    Description

    Always "list".

  • Name
    url
    Type
    string
    Description

    The endpoint path for this resource.

  • Name
    page
    Type
    integer
    Description

    The current page number (1-based).

  • Name
    per_page
    Type
    integer
    Description

    Merchants per page.

  • Name
    total_count
    Type
    integer
    Description

    Total merchants across all pages, after filters.

  • Name
    has_more
    Type
    boolean
    Description

    Whether further pages exist.

  • Name
    metadata
    Type
    object
    Description

    Carries summary (total_customers, active, paying, on_trial, all over the filtered set) and filters (the parameters that were applied).

  • Name
    data
    Type
    array
    Description

    The customer rows for this page.

Customer row

  • Name
    id
    Type
    integer
    Description

    The merchant's numeric id — use it to retrieve the full profile.

  • Name
    shop_id
    Type
    integer
    Description

    The Shopify shop id, or null when unknown.

  • Name
    name
    Type
    string
    Description

    Shop name, or null when unknown.

  • Name
    myshopify_domain
    Type
    string
    Description

    The *.myshopify.com domain, or null when unknown.

  • Name
    email
    Type
    string
    Description

    Shop contact email, or null when unknown.

  • Name
    country
    Type
    string
    Description

    Two-letter country code of the shop, or null when unknown.

  • Name
    install_status
    Type
    string
    Description

    One of active, uninstalled, frozen, closed.

  • Name
    is_test
    Type
    boolean
    Description

    Whether this is a test / development store.

  • Name
    is_on_trial
    Type
    boolean
    Description

    Whether the merchant is actively on a trial. current_mrr is reported as 0 while on trial.

  • Name
    plan_name
    Type
    string
    Description

    The current plan name, or Free Plan / No Plan when there is no active subscription.

  • Name
    subscription_status
    Type
    string
    Description

    The current subscription's status, or none when there is none.

  • Name
    billing_interval
    Type
    string
    Description

    e.g. EVERY_30_DAYS or ANNUAL, or null when there is no subscription.

  • Name
    current_mrr
    Type
    number
    Description

    Monthly recurring revenue in the plan currency; 0 while on trial or unpaid.

  • Name
    installed_at
    Type
    string
    Description

    ISO-8601 install timestamp, or null.

  • Name
    last_seen_at
    Type
    string
    Description

    ISO-8601 timestamp of the merchant's last activity, or null.

  • Name
    trial_ends_at
    Type
    string
    Description

    ISO-8601 trial end timestamp, or null.

  • Name
    tags
    Type
    array
    Description

    CRM tags on the merchant, each { "name", "color" }.

Customer object (retrieve)

Includes every field from the row (except the flattened plan_name / subscription_status / billing_interval / current_mrr, which move into plan), plus:

  • Name
    first_installed_at
    Type
    string
    Description

    ISO-8601 timestamp of the first install, or null.

  • Name
    last_uninstalled_at
    Type
    string
    Description

    ISO-8601 timestamp of the most recent uninstall, or null.

  • Name
    uninstall_reasons
    Type
    array
    Description

    Why the merchant left, from Shopify's uninstall survey — one entry per uninstall (a reinstall cycle produces several), newest uninstall first. Each entry is { "uninstalled_at", "reasons", "detail" }, and the array is [] when the merchant never uninstalled or never answered the survey.

    uninstalled_at is the ISO-8601 timestamp of that uninstall. reasons holds the answers as { "code", "label" } — the survey is multi-select, so one uninstall can carry several. Match on code, the stable canonical identifier (not_using_now, hard_to_set_up, expensive_or_unexpected_cost, not_worth_cost, limited_or_missing_features, found_better_app, not_satisfied_with_support, store_closing_or_pausing, scheduled_cancellation, other, …). Shopify's raw answer is localized across ~15 languages, so label is always the canonical English wording, never what the merchant saw; an answer not yet mapped to the taxonomy comes through as the raw Shopify string with code: null.

    detail is what the merchant typed under "Other (please specify)", whitespace-collapsed and capped at 300 characters, or null. Shopify's canned description for a scheduled_cancellation (the merchant closed or paused their store) is suppressed — it's a system string, not merchant feedback.

  • Name
    health_score
    Type
    number
    Description

    A 0–100 engagement/retention score for the merchant.

  • Name
    plan
    Type
    object
    Description

    name, subscription_status, billing_interval, amount, current_mrr, activated_at, cancelled_at, current_period_end. Values are null when the merchant has no subscription.

  • Name
    revenue
    Type
    object
    Description

    currency (always USD), lifetime_value, total_revenue, usage_revenue, and transaction_count.

  • Name
    custom_fields
    Type
    object
    Description

    Free-form key/value metadata you've set on the merchant (the CRM's metafields). {} when none.

  • Name
    contacts
    Type
    array
    Description

    Merchant contacts, each { "name", "email", "phone", "label" }, oldest first.

  • Name
    shop_admin_data
    Type
    object
    Description

    Shop admin details (plan_display_name, is_shopify_plus, is_partner_dev_store, shop_owner_name, primary_domain_url, currency_code, iana_timezone, country_code, city, province, zip, phone) when they've been fetched, otherwise null.

  • Name
    review
    Type
    object
    Description

    The merchant's most recent App Store review (id, rating, sentiment, date, body, has_reply, reply, reply_date, is_archived), or null when they haven't reviewed. See Reviews for the full review surface.


Pagination

The list is paginated. Use page and per_page (default 25, max 100) to walk the result set, and check has_more / total_count to know when to stop. total_count and metadata.summary are computed after your filters, so paging never changes them within a single filtered query.

For exporting the full customer list — into a warehouse, say — use cursor pagination instead: pass cursor (start with an empty value), read next_cursor from the response, and keep calling with it until next_cursor is null (or has_more is false). Cursor mode walks by opaque keyset, so it stays stable while merchants install or churn mid-export and isn't subject to the page-mode row cap. When you page by cursor, page and total_count come back null.


Errors

Beyond the universal 401 / 403 / 422 / 429 (see Errors):

  • The list never 404s — an app with no configured Partner API integration, or no merchants matching your filters, returns an empty data list.
  • The retrieve endpoint returns 404 resource_not_found when :customer isn't a merchant of :app (including a merchant on another of your apps).

sort and status are whitelisted, so an unknown token, a bad country code, or a malformed date surfaces as 422 validation_failed rather than 400.

Was this page helpful?