Scorecard & competitors

Two endpoints that frame an app against its rivals. GET /apps/:app/scorecard returns a single object: "scorecard" — a weighted overall health score, four sub-scores each paired with their breakdown, and prioritised improvement opportunities. GET /apps/:app/competitors returns the team's tracked (followed) competitors as a paginated object: "list", each row enriched with category and keyword overlap against your own app. Both read the scraped catalog, so neither is gated on the Shopify Partner integration.


GET/api/v1/apps/:app/scorecard

Scorecard

The app's composite competitive scorecard — the same computation the dashboard renders. Returns a weighted overall_score (0–100) plus four sub-scores: listing_quality, keyword_performance, reviews_reputation, and visibility. Each sub-score is a { score, breakdown } pair where breakdown carries the per-factor inputs (max-score weights, ratios, the outranked/unranked keyword lists, head-to-head review distribution, ranked-category positions, and so on). improvement_opportunities is a priority-ordered action list and competitor_comparison is the head-to-head against each followed competitor.

The scorecard is cached — the stored row is returned unless it is stale. Pass refresh=true to force a recompute.

is_provisional is true when the score was computed against an embedding-similar → top-category fallback set because the team follows no competitors yet; computed_at is the last-computed ISO-8601 timestamp, or null if never computed.

Path parameter

  • Name
    app
    Type
    string
    Description

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

Query parameters

  • Name
    refresh
    Type
    boolean
    Description

    Force a fresh recompute instead of returning the cached (non-stale) scorecard. Accepts the natural query forms (true/false/1/0/yes/no). Default false.

Request

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

Response

{
  "object": "scorecard",
  "overall_score": 72,
  "listing_quality": {
    "score": 68,
    "breakdown": {
      "characterUtilization": {
        "score": 28,
        "maxScore": 40,
        "averageUtilization": 71.2,
        "fields": {
          "name": { "length": 28, "limit": 30, "utilization": 93.3 },
          "subtitle": { "length": 60, "limit": 62, "utilization": 96.8 },
          "introduction": { "length": 90, "limit": 100, "utilization": 90 },
          "details": { "length": 320, "limit": 500, "utilization": 64 }
        },
        "featureCount": 3
      },
      "antiPatterns": {
        "score": 25,
        "maxScore": 30,
        "count": 1,
        "details": { "details": ["Avoid superlatives like \"best\"."] }
      },
      "keywordPresence": {
        "score": 15,
        "maxScore": 30,
        "found": ["inventory sync"],
        "missing": ["stock alerts"],
        "source": "traffic"
      }
    }
  },
  "keyword_performance": {
    "score": 74,
    "breakdown": {
      "keywordBreadth": { "score": 16, "maxScore": 20, "count": 41 },
      "outrankingRatio": { "score": 22, "maxScore": 35, "ratio": 0.63, "trackedWeightApplied": true },
      "topTenDensity": { "score": 21, "maxScore": 30, "count": 12, "total": 41, "ratio": 0.293, "trackedWeightApplied": true },
      "keywordCoverage": { "score": 11, "maxScore": 15, "ratio": 0.74 },
      "outrankedKeywords": [
        {
          "keyword_id": 5012,
          "keyword": "stock alerts",
          "yourRank": 14,
          "bestCompetitorRank": 3,
          "bestCompetitorAppName": "Stocky",
          "tracked": true,
          "installAttributed": false,
          "tier": 2,
          "gap": 11
        }
      ],
      "unrankedTrackedKeywords": [
        { "keyword_id": 5099, "keyword": "low stock", "installAttributed": true }
      ],
      "outrankedTrackedCount": 4,
      "unrankedTrackedCount": 2
    }
  },
  "reviews_reputation": {
    "score": 81,
    "breakdown": {
      "rating": { "score": 30, "maxScore": 35, "value": 4.7, "competitorAvg": 4.5 },
      "volume": { "score": 17, "maxScore": 20, "count": 312, "competitorAvg": 540 },
      "sentiment": {
        "score": 22,
        "maxScore": 25,
        "positiveRatio": 0.91,
        "distribution": { "positive": 284, "neutral": 12, "negative": 16 }
      },
      "recency": { "score": 12, "maxScore": 20 },
      "competitorAdjustment": 1
    }
  },
  "visibility": {
    "score": 65,
    "breakdown": {
      "categoriesRankedIn": {
        "score": 20,
        "maxScore": 25,
        "count": 4,
        "categories": [
          { "name": "Inventory management", "rank": 7 },
          { "name": "Stock control", "rank": 18 }
        ]
      },
      "bestPosition": { "score": 21, "maxScore": 30, "position": 7 },
      "avgPosition": { "score": 13, "maxScore": 25, "position": 12.5 },
      "categoryGaps": { "score": 11, "maxScore": 20, "gaps": 3 }
    }
  },
  "is_provisional": false,
  "computed_at": "2026-05-30T08:15:00+00:00",
  "improvement_opportunities": [
    {
      "type": "keyword_performance",
      "message": "Competitors outrank you on 4 keywords you track: stock alerts, low stock. Improve your listing relevance for these.",
      "priority": 1,
      "current_value": "37% win rate",
      "target_value": "50%+",
      "link_to": "/competitors",
      "keywords": [
        { "keyword": "stock alerts", "tracked": true, "installAttributed": false }
      ]
    },
    {
      "type": "listing_quality",
      "message": "Your app details field is significantly underutilized. This is the most impactful field for conversions.",
      "priority": 1,
      "current_value": "320 chars",
      "target_value": "500 chars",
      "link_to": "/suggestions/listings"
    }
  ],
  "competitor_comparison": [
    {
      "id": 4471,
      "name": "Stocky",
      "slug": "stocky",
      "logo_url": "https://cdn.shopify.com/app-store/listing/stocky/icon.png",
      "metrics": {
        "rating": { "yours": 4.7, "theirs": 4.5, "winner": "you" },
        "reviews": { "yours": 312, "theirs": 540, "winner": "them" },
        "keywords": { "yours": 41, "theirs": 38, "winner": "you" },
        "categories": { "yours": 4, "theirs": 3, "winner": "you" },
        "estInstalls": { "yours": 5200, "theirs": 9100, "winner": "them" }
      },
      "winsCount": 3
    }
  ]
}

Scorecard fields

  • Name
    object
    Type
    string
    Description

    Always "scorecard".

  • Name
    overall_score
    Type
    integer
    Description

    Weighted overall competitive health score, 0–100.

  • Name
    listing_quality
    Type
    object
    Description

    Listing-quality sub-score as { score, breakdown }. breakdown carries characterUtilization (per-field length/limit/utilization + featureCount), antiPatterns (count + per-field messages), and keywordPresence (found/missing lists + source).

  • Name
    keyword_performance
    Type
    object
    Description

    Keyword-performance sub-score as { score, breakdown }. breakdown carries keywordBreadth, outrankingRatio, topTenDensity, keywordCoverage, the outrankedKeywords and unrankedTrackedKeywords lists, and the outrankedTrackedCount / unrankedTrackedCount totals.

  • Name
    reviews_reputation
    Type
    object
    Description

    Reviews & reputation sub-score as { score, breakdown }. breakdown carries rating, volume, sentiment (with positive/neutral/negative distribution), recency, and the competitorAdjustment integer (−5..+5).

  • Name
    visibility
    Type
    object
    Description

    Visibility sub-score as { score, breakdown }. breakdown carries categoriesRankedIn (with per-category rank), bestPosition, avgPosition, and categoryGaps.

  • Name
    is_provisional
    Type
    boolean
    Description

    true when computed against a provisional comparison set (embedding-similar → top-category apps) because the team follows no competitors yet.

  • Name
    computed_at
    Type
    string
    Description

    When the scorecard was last computed (ISO-8601), or null if never.

  • Name
    improvement_opportunities
    Type
    array
    Description

    Prioritised opportunities (priority 1 = highest first). Each item: type (one of the four sub-scores), message, priority, current_value / target_value (string/int/null), link_to (string/null), and optionally keywords chips ({ keyword, tracked, installAttributed }) on keyword items.

  • Name
    competitor_comparison
    Type
    array
    Description

    Head-to-head against each followed competitor (empty when none are followed). Each item: id, name, slug, logo_url, a metrics map (rating/reviews/keywords/categories/estInstalls{ yours, theirs, winner } where winner is "you"/"them"/"tie"), and winsCount (0–5).


GET/api/v1/apps/:app/competitors

Competitors

The team's tracked (followed) competitors for this app, paginated as an object: "list". This is the followed set — the same one the dashboard and scorecard use — not catalog-similar discovery. Each row is a compact app card (id, slug, name, rating, reviews_count, est_installs, logo_url) plus five overlap stats against your own app (shared_categories, shared_keywords, relevance_score, category_avg_rank, keyword_avg_rank). The overlap fields are null when that tracked competitor is not also a catalog-competitor of your app; rows with a null relevance_score sort last on relevance.

When the team follows no competitors, a provisional set (embedding-similar → top-category apps) is returned and metadata.is_provisional is true. The endpoint reads the scraped catalog only, so it always returns a list — there is no Partner / BigQuery gate.

Path parameter

  • Name
    app
    Type
    string
    Description

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

Query parameters

  • Name
    sort
    Type
    string
    Description

    One of relevance (default), reviews, rating. Whitelisted — any other value returns 422.

  • Name
    order
    Type
    string
    Description

    asc or desc (default desc).

  • Name
    page
    Type
    integer
    Description

    The page to return (≥ 1). Default 1.

  • Name
    per_page
    Type
    integer
    Description

    Rows per page (1–100). Default 25.

Request

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

Response

{
  "object": "list",
  "url": "/api/v1/apps/01JN4HKQX0000000000000000/competitors",
  "page": 1,
  "per_page": 25,
  "total_count": 6,
  "has_more": false,
  "metadata": { "is_provisional": false },
  "data": [
    {
      "id": 4471,
      "slug": "stocky",
      "name": "Stocky",
      "rating": 4.5,
      "reviews_count": 540,
      "est_installs": 9100,
      "logo_url": "https://cdn.shopify.com/app-store/listing/stocky/icon.png",
      "shared_categories": 3,
      "shared_keywords": 27,
      "relevance_score": 84,
      "category_avg_rank": 6.4,
      "keyword_avg_rank": 9.1
    }
  ]
}

List envelope

The list envelope is shared across catalog collections. Competitor lists are not date-windowed, so the coverage block is omitted.

  • Name
    object
    Type
    string
    Description

    Always "list".

  • Name
    url
    Type
    string
    Description

    Path of this list resource.

  • Name
    page
    Type
    integer
    Description

    Current page number (1-based).

  • Name
    per_page
    Type
    integer
    Description

    Rows per page.

  • Name
    total_count
    Type
    integer
    Description

    Total rows across every page.

  • Name
    has_more
    Type
    boolean
    Description

    Whether more pages follow this one.

  • Name
    metadata
    Type
    object
    Description

    Endpoint-specific metadata; carries the is_provisional flag for this endpoint.

  • Name
    data
    Type
    array
    Description

    The competitor rows.

Competitor row

  • Name
    id
    Type
    integer
    Description

    Internal ShopifyApp id of the competitor.

  • Name
    slug
    Type
    string
    Description

    App Store slug (handle) of the competitor.

  • Name
    name
    Type
    string
    Description

    Competitor app name.

  • Name
    rating
    Type
    number
    Description

    Average star rating (rounded to 1 dp), or null when unrated.

  • Name
    reviews_count
    Type
    integer
    Description

    Total number of reviews, or null.

  • Name
    est_installs
    Type
    integer
    Description

    Estimated active installs, or null when not estimated.

  • Name
    logo_url
    Type
    string
    Description

    Competitor app icon URL, or null when not captured.

  • Name
    shared_categories
    Type
    integer
    Description

    Catalog categories shared with your app; null when the competitor is not one of your app's catalog-competitors.

  • Name
    shared_keywords
    Type
    integer
    Description

    Keywords both apps rank for; null when not a catalog-competitor.

  • Name
    relevance_score
    Type
    integer
    Description

    Catalog relevance score (higher = more similar); null when not a catalog-competitor. Null-score rows sort last on relevance.

  • Name
    category_avg_rank
    Type
    number
    Description

    Competitor's average rank across the shared categories (1 dp); null when not a catalog-competitor.

  • Name
    keyword_avg_rank
    Type
    number
    Description

    Competitor's average rank across the shared keywords (1 dp); null when not a catalog-competitor.


Errors

Beyond the universal 401 / 403 / 422 / 429 (see Errors), both endpoints return:

  • 404 resource_not_found — the addressed app does not exist or is not tracked by your team.

Neither endpoint reads the Shopify Partner integration or BigQuery — they serve from the scraped catalog — so neither returns the 409 partner errors documented for the metrics endpoints. A bad query parameter (an unknown sort token, a non-integer page, per_page over 100) surfaces as 422 validation_failed.

Was this page helpful?