From d3f22cb7e91db3cb59838fe3261452d274c39be7 Mon Sep 17 00:00:00 2001 From: Sidney Swift <158200036+sidneyswift@users.noreply.github.com> Date: Tue, 23 Jun 2026 02:21:38 -0400 Subject: [PATCH 1/5] refactor: collapse marketplace into one flat plugin; namespace internal skills Collapse the marketplace-of-3-plugins (recoup-skills + recoup-records + recoup-internal) into a single flat plugin rooted at the repo root (Option A). - skills/ is now the flat, de-duplicated union: 25 recoup-records skills + 6 internal skills, renamed to the 5-token recoup-internal-* convention and gated by a literal "recoup-internal" trigger keyword in each description. - Removed 15 legacy public skills: 11 functional twins of bundle skills (their capability lives on in recoup-* / recoup-internal-*) and 4 orphans with no bundle twin (chart-metric, song-writing, streaming-growth, trend-to-song), per the agreed bundle-only set. - Rehomed recoup-records' non-skill components to the repo root: agents/, hooks/, references/, templates/, fixtures/, RESOLVER.md, resolver-eval.jsonl. - Deleted the two nested plugin manifest trees and the whole plugins/ directory. - Manifests reduced to a single entry (recoup-skills, source "."), rebranded as the all-in-one and bumped to 2.0.0, across all four runtime manifests. - RESOLVER.md gains a Tier 3 "internal" section; resolver-eval.jsonl gains a recoup-internal-* routing fixture per internal skill. - Repointed check_resolvable.py / run_resolver_eval.py at the repo root and emptied scripts/vendored.json (no cross-plugin copies remain). - Updated README, AGENTS.md/CLAUDE.md, and the CI comment for the flat structure. Tradeoff (accepted): no more subset installs ("just records" / "just internal"), and the 6 internal skills now ship publicly via `npx skills add recoupable/skills` (gated, but installable). All five validation gates pass; final set = 31 skills. Co-Authored-By: Claude Opus 4.8 --- .agents/plugins/marketplace.json | 98 +- .claude-plugin/marketplace.json | 76 +- .claude-plugin/plugin.json | 12 +- .codex-plugin/plugin.json | 16 +- .github/workflows/validate.yml | 4 +- AGENTS.md | 72 +- README.md | 112 +- .../recoup-records/RESOLVER.md => RESOLVER.md | 26 +- .../agents => agents}/deal-qc-reviewer.md | 0 .../agents => agents}/market-scout.md | 0 .../agents => agents}/metadata-reconciler.md | 0 .../release-readiness-reviewer.md | 0 .../agents => agents}/research-analyst.md | 0 .../rights-chain-reviewer.md | 0 .../royalty-audit-analyst.md | 0 .../valuation-sensitivity-analyst.md | 0 .../source/00_overview/README.md | 0 .../source/01_track_list/track-list.csv | 0 .../02_compositions/composition-list.csv | 0 .../ascap/ascap-statements-2024.csv | 0 .../distributor/distributor-2024.csv | 0 .../03_royalty_statements/mlc/mlc-2024.csv | 0 .../soundexchange/soundexchange-2024.csv | 0 .../youtube/youtube-cid-2024.csv | 0 .../publishing-admin-agreement-2019.txt | 0 .../agreements/recording-agreement-2018.txt | 0 .../split_sheets/bright-lights-split.txt | 0 .../demo-release/assumptions.yaml | 0 .../fixtures => fixtures}/external-sources.md | 0 .../golden/apra-pdf/apra-2024.pdf | Bin .../apra-pdf/expected-royalty-ledger.csv | 0 .../golden/ascap-pdf/ascap-2024Q1.pdf | Bin .../ascap-pdf/expected-royalty-ledger.csv | 0 .../expected-royalty-ledger.csv | 0 .../golden/ascap-performance/input.csv | 0 .../expected-royalty-ledger.csv | 0 .../golden/bmi-performance/input.csv | 0 .../expected-royalty-ledger.csv | 0 .../bmi-real-headers/expected-status.json | 0 .../golden/bmi-real-headers/input.csv | 0 .../curve-income/expected-royalty-ledger.csv | 0 .../golden/curve-income/input.csv | 0 .../direct-sync/expected-royalty-ledger.csv | 0 .../golden/direct-sync/input.csv | 0 .../expected-royalty-ledger.csv | 0 .../golden/distributor-master/input.csv | 0 .../golden/generate-pdf-fixtures.py | 0 .../hfa-pdf/expected-royalty-ledger.csv | 0 .../golden/hfa-pdf/hfa-statement.pdf | Bin .../jasrac-pdf/expected-royalty-ledger.csv | 0 .../golden/jasrac-pdf/jasrac-2024.pdf | Bin .../expected-royalty-ledger.csv | 0 .../golden/mlc-mechanical/input.csv | 0 .../prs-pdf/expected-royalty-ledger.csv | 0 .../golden/prs-pdf/prs-2024.pdf | Bin .../expected-royalty-ledger.csv | 0 .../golden/publisher-admin/input.csv | 0 .../sacem-pdf/expected-royalty-ledger.csv | 0 .../golden/sacem-pdf/sacem-2024Q2.pdf | Bin .../sadaic-pdf/expected-royalty-ledger.csv | 0 .../golden/sadaic-pdf/sadaic-2024.pdf | Bin .../socan-pdf/expected-royalty-ledger.csv | 0 .../golden/socan-pdf/socan-2024.pdf | Bin .../expected-royalty-ledger.csv | 0 .../soundexchange-pdf/soundexchange-2024.pdf | Bin .../soundexchange/expected-royalty-ledger.csv | 0 .../golden/soundexchange/input.csv | 0 .../expected-royalty-ledger.csv | 0 .../golden/youtube-content-id/input.csv | 0 .../hooks => hooks}/check-env.sh | 0 .../recoup-records/hooks => hooks}/hooks.json | 0 .../hooks => hooks}/protect-source-files.sh | 0 .../.claude-plugin/plugin.json | 27 - .../recoup-internal/.codex-plugin/plugin.json | 38 - .../.cursor-plugin/plugin.json | 27 - plugins/recoup-internal/LICENSE | 674 - plugins/recoup-internal/README.md | 60 - .../recoup-internal/assets/recoup-icon.png | Bin 129041 -> 0 bytes .../references/frontier-benchmark.md | 303 - .../recoup-records/.claude-plugin/plugin.json | 37 - .../recoup-records/.codex-plugin/plugin.json | 39 - .../recoup-records/.cursor-plugin/plugin.json | 39 - plugins/recoup-records/LICENSE | 674 - plugins/recoup-records/README.md | 38 - plugins/recoup-records/assets/recoup-icon.png | Bin 129041 -> 0 bytes .../account-resolver.md | 0 .../references => references}/analyze-gate.md | 0 .../references => references}/content-api.md | 0 .../deal-workflow.md | 0 .../deal-workspace.md | 0 .../references => references}/endpoints.md | 0 .../references => references}/flamingo-api.md | 0 .../normalization.md | 0 .../references => references}/red-flags.md | 0 .../research-context.md | 0 .../response-shapes.md | 0 .../song-sourcing.md | 0 .../tooling-landscape.md | 0 .../references => references}/workflows.md | 0 .../workspace-context.md | 0 ...resolver-eval.jsonl => resolver-eval.jsonl | 8 + scripts/check_resolvable.py | 8 +- scripts/run_resolver_eval.py | 8 +- scripts/vendored.json | 11 +- skills/catalog-value-estimator/SKILL.md | 214 - .../references/methodology.md | 156 - .../references/recoup-api.md | 148 - .../scripts/build_report.py | 144 - .../scripts/estimate.py | 421 - .../scripts/fetch_album_tracks.py | 23 - skills/chart-metric/README.md | 59 - skills/chart-metric/SKILL.md | 265 - .../references/advanced-workflows.md | 314 - skills/chart-metric/references/api_data.json | 35054 ---------------- .../chart-metric/references/curl-fallback.md | 339 - skills/chart-metric/references/endpoints.md | 264 - .../references/parameter-guide.md | 470 - .../chart-metric/scripts/discover_artists.py | 262 - skills/chart-metric/scripts/get_artist.py | 52 - .../chart-metric/scripts/get_artist_albums.py | 60 - .../scripts/get_artist_audience.py | 72 - .../scripts/get_artist_by_spotify.py | 68 - .../chart-metric/scripts/get_artist_career.py | 49 - .../chart-metric/scripts/get_artist_cities.py | 74 - .../scripts/get_artist_insights.py | 58 - .../scripts/get_artist_instagram_posts.py | 54 - .../scripts/get_artist_metrics.py | 87 - .../scripts/get_artist_playlists.py | 234 - .../chart-metric/scripts/get_artist_tracks.py | 60 - .../chart-metric/scripts/get_artist_urls.py | 70 - skills/chart-metric/scripts/get_curator.py | 77 - skills/chart-metric/scripts/get_playlist.py | 51 - .../scripts/get_similar_artists.py | 195 - skills/chart-metric/scripts/get_token.py | 59 - skills/chart-metric/scripts/get_track.py | 49 - .../scripts/get_track_by_spotify.py | 60 - skills/chart-metric/scripts/list_festivals.py | 42 - skills/chart-metric/scripts/list_genres.py | 42 - skills/chart-metric/scripts/search_artist.py | 82 - skills/content-creation/SKILL.md | 153 - skills/issue-implementation/SKILL.md | 130 - skills/issue-management/SKILL.md | 129 - skills/music-industry-research/SKILL.md | 113 - .../references/endpoints.md | 317 - .../references/response-shapes.md | 260 - .../references/workflows.md | 657 - skills/recoup-account-status/SKILL.md | 124 - .../scripts/generate_account_status.py | 464 - .../recoup-catalog-estimate-value/SKILL.md | 0 .../references/methodology.md | 0 .../references/recoup-api.md | 0 .../scripts/build_report.py | 0 .../scripts/estimate.py | 0 .../scripts/fetch_album_tracks.py | 0 .../recoup-catalog-review-deal/SKILL.md | 0 .../source/00_overview/README.md | 0 .../source/01_track_list/track-list.csv | 0 .../02_compositions/composition-list.csv | 0 .../ascap/ascap-statements-2024.csv | 0 .../distributor/distributor-2024.csv | 0 .../03_royalty_statements/mlc/mlc-2024.csv | 0 .../soundexchange/soundexchange-2024.csv | 0 .../youtube/youtube-cid-2024.csv | 0 .../publishing-admin-agreement-2019.txt | 0 .../agreements/recording-agreement-2018.txt | 0 .../split_sheets/bright-lights-split.txt | 0 .../references/canonical-schema.md | 0 .../references/cleaning-rules.md | 0 .../references/data-room-checklist.md | 0 .../references/deal-workflow.md | 0 .../references/deal-workspace.md | 0 .../references/financing-underwrite.md | 0 .../references/methodology.md | 0 .../references/normalization.md | 0 .../references/output-templates.md | 0 .../references/post-close-admin.md | 0 .../references/pro-performance-income.md | 0 .../references/recoup-api.md | 0 .../references/red-flags.md | 0 .../references/rights-review.md | 0 .../references/royalty-audit.md | 0 .../references/seller-prep.md | 0 .../references/valuation-framework.md | 0 .../scripts/_helpers.py | 0 .../scripts/auto-column-map.py | 0 .../scripts/build-deal-readiness.py | 0 .../scripts/build-file-manifest.py | 0 .../scripts/build-manual-review-queue.py | 0 .../scripts/calculate-concentration.py | 0 .../scripts/calculate-nps-nls-bridge.py | 0 .../scripts/dataroom-hygiene-scan.py | 0 .../scripts/extract-pdf-statement.py | 0 .../scripts/normalize-royalty-statement.py | 0 .../scripts/run-deal-checks.py | 0 .../scripts/validate-dashboard.py | 0 .../scripts/validate-deal-workspace.py | 0 .../scripts/validate-evidence-ledger.py | 0 .../scripts/validate-findings-evidence.py | 0 .../scripts/validate-normalized-ledger.py | 0 .../scripts/validate-workspace-consistency.py | 0 .../templates/assumptions.yaml | 0 .../templates/deal-workspace/assumptions.yaml | 0 .../deal-workspace/evidence-ledger.json | 0 .../templates/deal-workspace/findings.json | 0 .../deal-workspace/memos/financing-pack.md | 0 .../templates/deal-workspace/memos/ic-memo.md | 0 .../memos/seller-cleanup-report.md | 0 .../templates/deal-workspace/missing-files.md | 0 .../templates/evidence-ledger.json | 0 .../templates/findings.json | 0 .../templates/memos/financing-pack.md | 0 .../templates/memos/ic-memo.md | 0 .../templates/memos/seller-cleanup-report.md | 0 .../templates/missing-files.md | 0 .../recoup-content-asset-pack/SKILL.md | 0 .../references/account-resolver.md | 0 .../references/analyze-gate.md | 0 .../references/content-api.md | 0 .../references/research-context.md | 0 .../references/song-sourcing.md | 0 .../references/workspace-context.md | 0 .../recoup-content-make-graphics/SKILL.md | 0 .../references/account-resolver.md | 0 .../references/analyze-gate.md | 0 .../references/content-api.md | 0 .../references/research-context.md | 0 .../references/song-sourcing.md | 0 .../references/workspace-context.md | 0 .../recoup-content-make-video/SKILL.md | 0 .../references/account-resolver.md | 0 .../references/analyze-gate.md | 0 .../references/content-api.md | 0 .../references/research-context.md | 0 .../references/song-sourcing.md | 0 .../references/workspace-context.md | 0 .../recoup-content-reactive-post/SKILL.md | 0 .../references/account-resolver.md | 0 .../references/analyze-gate.md | 0 .../references/content-api.md | 0 .../references/research-context.md | 0 .../references/song-sourcing.md | 0 .../references/workspace-context.md | 0 .../recoup-content-write-caption/SKILL.md | 0 .../references/account-resolver.md | 0 .../references/analyze-gate.md | 0 .../references/content-api.md | 0 .../references/research-context.md | 0 .../references/song-sourcing.md | 0 .../references/workspace-context.md | 0 .../SKILL.md | 6 +- .../scripts/generate_account_status.py | 0 .../SKILL.md | 4 +- .../recoup-internal-dev-ship-issue}/SKILL.md | 12 +- .../SKILL.md | 6 +- .../references/frontier-benchmark.md | 2 +- .../references/scorecard.md | 0 .../scripts/measure.py | 2 +- .../SKILL.md | 6 +- .../fixtures/Neon-Tide-valuation.pdf | Bin .../fixtures/example-lead.json | 0 .../fixtures/example-pipeline-brief.md | 0 .../references/attio-funnel.md | 0 .../references/qualification-rubric.md | 0 .../references/recoup-valuation-api.md | 0 .../scripts/fetch_catalog.py | 0 .../scripts/render_valuation_pdf.py | 0 .../templates/outreach-email.md | 0 .../SKILL.md | 4 +- .../recoup-platform-api-access/SKILL.md | 0 .../recoup-platform-build-workspace/SKILL.md | 0 .../recoup-platform-capture-lesson/SKILL.md | 0 .../recoup-platform-connect-account/SKILL.md | 0 .../recoup-release-plan-rollout/SKILL.md | 0 .../references/deliverables.md | 0 .../references/endpoints.md | 0 .../references/release-template.md | 0 .../references/release-workspace.md | 0 .../references/response-shapes.md | 0 .../references/section-guide.md | 0 .../scripts/build_campaign_timeline.py | 0 .../scripts/validate_release.py | 0 .../templates/release-workspace/RELEASE.md | 0 .../release-workspace/assumptions.yaml | 0 .../release-workspace/brief/.gitkeep | 0 .../release-workspace/campaign/.gitkeep | 0 .../release-workspace/reports/.gitkeep | 0 .../release-workspace/targeting/.gitkeep | 0 .../release-workspace/tracking/.gitkeep | 0 .../recoup-release-track-drop/SKILL.md | 0 .../references/response-shapes.md | 0 .../recoup-research-artist-overview/SKILL.md | 0 .../references/endpoints.md | 0 .../references/response-shapes.md | 0 .../references/workflows.md | 0 .../recoup-research-find-contacts/SKILL.md | 0 .../references/workflows.md | 0 .../recoup-research-find-talent/SKILL.md | 0 .../references/workflows.md | 0 .../recoup-research-playlist-targets/SKILL.md | 0 .../references/workflows.md | 0 .../recoup-research-the-web/SKILL.md | 0 .../references/workflows.md | 0 .../recoup-research-weekly-brief/SKILL.md | 0 .../references/workflows.md | 0 .../recoup-roster-add-artist/SKILL.md | 0 .../references/artist-example.md | 0 .../references/artist-template.md | 0 .../references/audience-template.md | 0 .../references/release-template.md | 0 .../recoup-roster-list-artists/SKILL.md | 0 .../recoup-roster-manage-artist/SKILL.md | 0 .../recoup-song-analyze-audio/SKILL.md | 0 .../references/flamingo-api.md | 0 .../recoup-song-find-hook/SKILL.md | 0 .../references/flamingo-api.md | 0 .../recoup-song-placement-pitch/SKILL.md | 0 .../references/flamingo-api.md | 0 skills/release-management/README.md | 66 - skills/release-management/SKILL.md | 218 - .../references/deliverables.md | 368 - .../references/release-template.md | 545 - .../references/section-guide.md | 634 - skills/short-video/SKILL.md | 73 - .../references/short-video-manual.md | 185 - .../short-video/references/song-sourcing.md | 34 - skills/skill-pack-benchmark/SKILL.md | 155 - .../references/scorecard.md | 79 - .../skill-pack-benchmark/scripts/measure.py | 350 - skills/social-posts/SKILL.md | 111 - skills/song-writing/SKILL.md | 37 - skills/streaming-growth/SKILL.md | 174 - skills/trend-to-song/SKILL.md | 172 - skills/valuation-sales-pipeline/SKILL.md | 188 - .../fixtures/Neon-Tide-valuation.pdf | Bin 7155 -> 0 bytes .../fixtures/example-lead.json | 95 - .../fixtures/example-pipeline-brief.md | 45 - .../references/attio-funnel.md | 112 - .../references/qualification-rubric.md | 56 - .../references/recoup-valuation-api.md | 118 - .../scripts/fetch_catalog.py | 146 - .../scripts/render_valuation_pdf.py | 405 - .../templates/outreach-email.md | 46 - .../deal-workspace/assumptions.yaml | 0 .../deal-workspace/evidence-ledger.json | 0 .../deal-workspace/findings.json | 0 .../deal-workspace/memos/financing-pack.md | 0 .../deal-workspace/memos/ic-memo.md | 0 .../memos/seller-cleanup-report.md | 0 .../deal-workspace/missing-files.md | 0 349 files changed, 223 insertions(+), 48713 deletions(-) rename plugins/recoup-records/RESOLVER.md => RESOLVER.md (75%) rename {plugins/recoup-records/agents => agents}/deal-qc-reviewer.md (100%) rename {plugins/recoup-records/agents => agents}/market-scout.md (100%) rename {plugins/recoup-records/agents => agents}/metadata-reconciler.md (100%) rename {plugins/recoup-records/agents => agents}/release-readiness-reviewer.md (100%) rename {plugins/recoup-records/agents => agents}/research-analyst.md (100%) rename {plugins/recoup-records/agents => agents}/rights-chain-reviewer.md (100%) rename {plugins/recoup-records/agents => agents}/royalty-audit-analyst.md (100%) rename {plugins/recoup-records/agents => agents}/valuation-sensitivity-analyst.md (100%) rename {plugins/recoup-records/fixtures => fixtures}/demo-data-room/source/00_overview/README.md (100%) rename {plugins/recoup-records/fixtures => fixtures}/demo-data-room/source/01_track_list/track-list.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/demo-data-room/source/02_compositions/composition-list.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/demo-data-room/source/03_royalty_statements/ascap/ascap-statements-2024.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/demo-data-room/source/03_royalty_statements/distributor/distributor-2024.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/demo-data-room/source/03_royalty_statements/mlc/mlc-2024.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/demo-data-room/source/03_royalty_statements/soundexchange/soundexchange-2024.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/demo-data-room/source/03_royalty_statements/youtube/youtube-cid-2024.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/demo-data-room/source/04_rights_documents/agreements/publishing-admin-agreement-2019.txt (100%) rename {plugins/recoup-records/fixtures => fixtures}/demo-data-room/source/04_rights_documents/agreements/recording-agreement-2018.txt (100%) rename {plugins/recoup-records/fixtures => fixtures}/demo-data-room/source/04_rights_documents/split_sheets/bright-lights-split.txt (100%) rename {plugins/recoup-records/fixtures => fixtures}/demo-release/assumptions.yaml (100%) rename {plugins/recoup-records/fixtures => fixtures}/external-sources.md (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/apra-pdf/apra-2024.pdf (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/apra-pdf/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/ascap-pdf/ascap-2024Q1.pdf (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/ascap-pdf/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/ascap-performance/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/ascap-performance/input.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/bmi-performance/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/bmi-performance/input.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/bmi-real-headers/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/bmi-real-headers/expected-status.json (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/bmi-real-headers/input.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/curve-income/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/curve-income/input.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/direct-sync/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/direct-sync/input.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/distributor-master/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/distributor-master/input.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/generate-pdf-fixtures.py (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/hfa-pdf/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/hfa-pdf/hfa-statement.pdf (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/jasrac-pdf/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/jasrac-pdf/jasrac-2024.pdf (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/mlc-mechanical/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/mlc-mechanical/input.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/prs-pdf/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/prs-pdf/prs-2024.pdf (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/publisher-admin/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/publisher-admin/input.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/sacem-pdf/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/sacem-pdf/sacem-2024Q2.pdf (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/sadaic-pdf/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/sadaic-pdf/sadaic-2024.pdf (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/socan-pdf/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/socan-pdf/socan-2024.pdf (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/soundexchange-pdf/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/soundexchange-pdf/soundexchange-2024.pdf (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/soundexchange/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/soundexchange/input.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/youtube-content-id/expected-royalty-ledger.csv (100%) rename {plugins/recoup-records/fixtures => fixtures}/golden/youtube-content-id/input.csv (100%) rename {plugins/recoup-records/hooks => hooks}/check-env.sh (100%) rename {plugins/recoup-records/hooks => hooks}/hooks.json (100%) rename {plugins/recoup-records/hooks => hooks}/protect-source-files.sh (100%) delete mode 100644 plugins/recoup-internal/.claude-plugin/plugin.json delete mode 100644 plugins/recoup-internal/.codex-plugin/plugin.json delete mode 100644 plugins/recoup-internal/.cursor-plugin/plugin.json delete mode 100644 plugins/recoup-internal/LICENSE delete mode 100644 plugins/recoup-internal/README.md delete mode 100644 plugins/recoup-internal/assets/recoup-icon.png delete mode 100644 plugins/recoup-internal/skills/recoup-eval-skill-benchmark/references/frontier-benchmark.md delete mode 100644 plugins/recoup-records/.claude-plugin/plugin.json delete mode 100644 plugins/recoup-records/.codex-plugin/plugin.json delete mode 100644 plugins/recoup-records/.cursor-plugin/plugin.json delete mode 100644 plugins/recoup-records/LICENSE delete mode 100644 plugins/recoup-records/README.md delete mode 100644 plugins/recoup-records/assets/recoup-icon.png rename {plugins/recoup-records/references => references}/account-resolver.md (100%) rename {plugins/recoup-records/references => references}/analyze-gate.md (100%) rename {plugins/recoup-records/references => references}/content-api.md (100%) rename {plugins/recoup-records/references => references}/deal-workflow.md (100%) rename {plugins/recoup-records/references => references}/deal-workspace.md (100%) rename {plugins/recoup-records/references => references}/endpoints.md (100%) rename {plugins/recoup-records/references => references}/flamingo-api.md (100%) rename {plugins/recoup-records/references => references}/normalization.md (100%) rename {plugins/recoup-records/references => references}/red-flags.md (100%) rename {plugins/recoup-records/references => references}/research-context.md (100%) rename {plugins/recoup-records/references => references}/response-shapes.md (100%) rename {plugins/recoup-records/references => references}/song-sourcing.md (100%) rename {plugins/recoup-records/references => references}/tooling-landscape.md (100%) rename {plugins/recoup-records/references => references}/workflows.md (100%) rename {plugins/recoup-records/references => references}/workspace-context.md (100%) rename plugins/recoup-records/resolver-eval.jsonl => resolver-eval.jsonl (83%) delete mode 100644 skills/catalog-value-estimator/SKILL.md delete mode 100644 skills/catalog-value-estimator/references/methodology.md delete mode 100644 skills/catalog-value-estimator/references/recoup-api.md delete mode 100755 skills/catalog-value-estimator/scripts/build_report.py delete mode 100755 skills/catalog-value-estimator/scripts/estimate.py delete mode 100755 skills/catalog-value-estimator/scripts/fetch_album_tracks.py delete mode 100644 skills/chart-metric/README.md delete mode 100644 skills/chart-metric/SKILL.md delete mode 100644 skills/chart-metric/references/advanced-workflows.md delete mode 100644 skills/chart-metric/references/api_data.json delete mode 100644 skills/chart-metric/references/curl-fallback.md delete mode 100644 skills/chart-metric/references/endpoints.md delete mode 100644 skills/chart-metric/references/parameter-guide.md delete mode 100644 skills/chart-metric/scripts/discover_artists.py delete mode 100644 skills/chart-metric/scripts/get_artist.py delete mode 100644 skills/chart-metric/scripts/get_artist_albums.py delete mode 100644 skills/chart-metric/scripts/get_artist_audience.py delete mode 100644 skills/chart-metric/scripts/get_artist_by_spotify.py delete mode 100644 skills/chart-metric/scripts/get_artist_career.py delete mode 100644 skills/chart-metric/scripts/get_artist_cities.py delete mode 100644 skills/chart-metric/scripts/get_artist_insights.py delete mode 100644 skills/chart-metric/scripts/get_artist_instagram_posts.py delete mode 100644 skills/chart-metric/scripts/get_artist_metrics.py delete mode 100644 skills/chart-metric/scripts/get_artist_playlists.py delete mode 100644 skills/chart-metric/scripts/get_artist_tracks.py delete mode 100644 skills/chart-metric/scripts/get_artist_urls.py delete mode 100644 skills/chart-metric/scripts/get_curator.py delete mode 100644 skills/chart-metric/scripts/get_playlist.py delete mode 100644 skills/chart-metric/scripts/get_similar_artists.py delete mode 100644 skills/chart-metric/scripts/get_token.py delete mode 100644 skills/chart-metric/scripts/get_track.py delete mode 100644 skills/chart-metric/scripts/get_track_by_spotify.py delete mode 100644 skills/chart-metric/scripts/list_festivals.py delete mode 100644 skills/chart-metric/scripts/list_genres.py delete mode 100644 skills/chart-metric/scripts/search_artist.py delete mode 100644 skills/content-creation/SKILL.md delete mode 100644 skills/issue-implementation/SKILL.md delete mode 100644 skills/issue-management/SKILL.md delete mode 100644 skills/music-industry-research/SKILL.md delete mode 100644 skills/music-industry-research/references/endpoints.md delete mode 100644 skills/music-industry-research/references/response-shapes.md delete mode 100644 skills/music-industry-research/references/workflows.md delete mode 100644 skills/recoup-account-status/SKILL.md delete mode 100644 skills/recoup-account-status/scripts/generate_account_status.py rename {plugins/recoup-records/skills => skills}/recoup-catalog-estimate-value/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-estimate-value/references/methodology.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-estimate-value/references/recoup-api.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-estimate-value/scripts/build_report.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-estimate-value/scripts/estimate.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-estimate-value/scripts/fetch_album_tracks.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/fixtures/demo-data-room/source/00_overview/README.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/fixtures/demo-data-room/source/01_track_list/track-list.csv (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/fixtures/demo-data-room/source/02_compositions/composition-list.csv (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/ascap/ascap-statements-2024.csv (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/distributor/distributor-2024.csv (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/mlc/mlc-2024.csv (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/soundexchange/soundexchange-2024.csv (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/youtube/youtube-cid-2024.csv (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/fixtures/demo-data-room/source/04_rights_documents/agreements/publishing-admin-agreement-2019.txt (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/fixtures/demo-data-room/source/04_rights_documents/agreements/recording-agreement-2018.txt (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/fixtures/demo-data-room/source/04_rights_documents/split_sheets/bright-lights-split.txt (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/canonical-schema.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/cleaning-rules.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/data-room-checklist.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/deal-workflow.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/deal-workspace.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/financing-underwrite.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/methodology.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/normalization.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/output-templates.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/post-close-admin.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/pro-performance-income.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/recoup-api.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/red-flags.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/rights-review.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/royalty-audit.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/seller-prep.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/references/valuation-framework.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/_helpers.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/auto-column-map.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/build-deal-readiness.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/build-file-manifest.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/build-manual-review-queue.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/calculate-concentration.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/calculate-nps-nls-bridge.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/dataroom-hygiene-scan.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/extract-pdf-statement.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/normalize-royalty-statement.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/run-deal-checks.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/validate-dashboard.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/validate-deal-workspace.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/validate-evidence-ledger.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/validate-findings-evidence.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/validate-normalized-ledger.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/scripts/validate-workspace-consistency.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/templates/assumptions.yaml (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/templates/deal-workspace/assumptions.yaml (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/templates/deal-workspace/evidence-ledger.json (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/templates/deal-workspace/findings.json (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/templates/deal-workspace/memos/financing-pack.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/templates/deal-workspace/memos/ic-memo.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/templates/deal-workspace/memos/seller-cleanup-report.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/templates/deal-workspace/missing-files.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/templates/evidence-ledger.json (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/templates/findings.json (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/templates/memos/financing-pack.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/templates/memos/ic-memo.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/templates/memos/seller-cleanup-report.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-catalog-review-deal/templates/missing-files.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-asset-pack/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-asset-pack/references/account-resolver.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-asset-pack/references/analyze-gate.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-asset-pack/references/content-api.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-asset-pack/references/research-context.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-asset-pack/references/song-sourcing.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-asset-pack/references/workspace-context.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-make-graphics/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-make-graphics/references/account-resolver.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-make-graphics/references/analyze-gate.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-make-graphics/references/content-api.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-make-graphics/references/research-context.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-make-graphics/references/song-sourcing.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-make-graphics/references/workspace-context.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-make-video/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-make-video/references/account-resolver.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-make-video/references/analyze-gate.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-make-video/references/content-api.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-make-video/references/research-context.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-make-video/references/song-sourcing.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-make-video/references/workspace-context.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-reactive-post/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-reactive-post/references/account-resolver.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-reactive-post/references/analyze-gate.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-reactive-post/references/content-api.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-reactive-post/references/research-context.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-reactive-post/references/song-sourcing.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-reactive-post/references/workspace-context.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-write-caption/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-write-caption/references/account-resolver.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-write-caption/references/analyze-gate.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-write-caption/references/content-api.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-write-caption/references/research-context.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-write-caption/references/song-sourcing.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-content-write-caption/references/workspace-context.md (100%) rename {plugins/recoup-internal/skills/recoup-account-health-report => skills/recoup-internal-account-health-report}/SKILL.md (95%) rename {plugins/recoup-internal/skills/recoup-account-health-report => skills/recoup-internal-account-health-report}/scripts/generate_account_status.py (100%) rename {plugins/recoup-internal/skills/recoup-dev-issue-tracker => skills/recoup-internal-dev-issue-tracker}/SKILL.md (93%) rename {plugins/recoup-internal/skills/recoup-dev-ship-issue => skills/recoup-internal-dev-ship-issue}/SKILL.md (83%) rename {plugins/recoup-internal/skills/recoup-eval-skill-benchmark => skills/recoup-internal-eval-skill-benchmark}/SKILL.md (96%) rename skills/{skill-pack-benchmark => recoup-internal-eval-skill-benchmark}/references/frontier-benchmark.md (99%) rename {plugins/recoup-internal/skills/recoup-eval-skill-benchmark => skills/recoup-internal-eval-skill-benchmark}/references/scorecard.md (100%) rename {plugins/recoup-internal/skills/recoup-eval-skill-benchmark => skills/recoup-internal-eval-skill-benchmark}/scripts/measure.py (99%) rename {plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline => skills/recoup-internal-funnel-valuation-pipeline}/SKILL.md (97%) rename {plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline => skills/recoup-internal-funnel-valuation-pipeline}/fixtures/Neon-Tide-valuation.pdf (100%) rename {plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline => skills/recoup-internal-funnel-valuation-pipeline}/fixtures/example-lead.json (100%) rename {plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline => skills/recoup-internal-funnel-valuation-pipeline}/fixtures/example-pipeline-brief.md (100%) rename {plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline => skills/recoup-internal-funnel-valuation-pipeline}/references/attio-funnel.md (100%) rename {plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline => skills/recoup-internal-funnel-valuation-pipeline}/references/qualification-rubric.md (100%) rename {plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline => skills/recoup-internal-funnel-valuation-pipeline}/references/recoup-valuation-api.md (100%) rename {plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline => skills/recoup-internal-funnel-valuation-pipeline}/scripts/fetch_catalog.py (100%) rename {plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline => skills/recoup-internal-funnel-valuation-pipeline}/scripts/render_valuation_pdf.py (100%) rename {plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline => skills/recoup-internal-funnel-valuation-pipeline}/templates/outreach-email.md (100%) rename {plugins/recoup-internal/skills/recoup-social-ship-posts => skills/recoup-internal-social-ship-posts}/SKILL.md (90%) rename {plugins/recoup-records/skills => skills}/recoup-platform-api-access/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-platform-build-workspace/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-platform-capture-lesson/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-platform-connect-account/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/references/deliverables.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/references/endpoints.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/references/release-template.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/references/release-workspace.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/references/response-shapes.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/references/section-guide.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/scripts/build_campaign_timeline.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/scripts/validate_release.py (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/templates/release-workspace/RELEASE.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/templates/release-workspace/assumptions.yaml (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/templates/release-workspace/brief/.gitkeep (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/templates/release-workspace/campaign/.gitkeep (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/templates/release-workspace/reports/.gitkeep (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/templates/release-workspace/targeting/.gitkeep (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-plan-rollout/templates/release-workspace/tracking/.gitkeep (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-track-drop/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-release-track-drop/references/response-shapes.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-research-artist-overview/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-research-artist-overview/references/endpoints.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-research-artist-overview/references/response-shapes.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-research-artist-overview/references/workflows.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-research-find-contacts/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-research-find-contacts/references/workflows.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-research-find-talent/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-research-find-talent/references/workflows.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-research-playlist-targets/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-research-playlist-targets/references/workflows.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-research-the-web/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-research-the-web/references/workflows.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-research-weekly-brief/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-research-weekly-brief/references/workflows.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-roster-add-artist/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-roster-add-artist/references/artist-example.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-roster-add-artist/references/artist-template.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-roster-add-artist/references/audience-template.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-roster-add-artist/references/release-template.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-roster-list-artists/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-roster-manage-artist/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-song-analyze-audio/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-song-analyze-audio/references/flamingo-api.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-song-find-hook/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-song-find-hook/references/flamingo-api.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-song-placement-pitch/SKILL.md (100%) rename {plugins/recoup-records/skills => skills}/recoup-song-placement-pitch/references/flamingo-api.md (100%) delete mode 100644 skills/release-management/README.md delete mode 100644 skills/release-management/SKILL.md delete mode 100644 skills/release-management/references/deliverables.md delete mode 100644 skills/release-management/references/release-template.md delete mode 100644 skills/release-management/references/section-guide.md delete mode 100644 skills/short-video/SKILL.md delete mode 100644 skills/short-video/references/short-video-manual.md delete mode 100644 skills/short-video/references/song-sourcing.md delete mode 100644 skills/skill-pack-benchmark/SKILL.md delete mode 100644 skills/skill-pack-benchmark/references/scorecard.md delete mode 100644 skills/skill-pack-benchmark/scripts/measure.py delete mode 100644 skills/social-posts/SKILL.md delete mode 100644 skills/song-writing/SKILL.md delete mode 100644 skills/streaming-growth/SKILL.md delete mode 100644 skills/trend-to-song/SKILL.md delete mode 100644 skills/valuation-sales-pipeline/SKILL.md delete mode 100644 skills/valuation-sales-pipeline/fixtures/Neon-Tide-valuation.pdf delete mode 100644 skills/valuation-sales-pipeline/fixtures/example-lead.json delete mode 100644 skills/valuation-sales-pipeline/fixtures/example-pipeline-brief.md delete mode 100644 skills/valuation-sales-pipeline/references/attio-funnel.md delete mode 100644 skills/valuation-sales-pipeline/references/qualification-rubric.md delete mode 100644 skills/valuation-sales-pipeline/references/recoup-valuation-api.md delete mode 100644 skills/valuation-sales-pipeline/scripts/fetch_catalog.py delete mode 100644 skills/valuation-sales-pipeline/scripts/render_valuation_pdf.py delete mode 100644 skills/valuation-sales-pipeline/templates/outreach-email.md rename {plugins/recoup-records/templates => templates}/deal-workspace/assumptions.yaml (100%) rename {plugins/recoup-records/templates => templates}/deal-workspace/evidence-ledger.json (100%) rename {plugins/recoup-records/templates => templates}/deal-workspace/findings.json (100%) rename {plugins/recoup-records/templates => templates}/deal-workspace/memos/financing-pack.md (100%) rename {plugins/recoup-records/templates => templates}/deal-workspace/memos/ic-memo.md (100%) rename {plugins/recoup-records/templates => templates}/deal-workspace/memos/seller-cleanup-report.md (100%) rename {plugins/recoup-records/templates => templates}/deal-workspace/missing-files.md (100%) diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index aac92bb..7f95170 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -10,8 +10,8 @@ "name": "recoup-skills", "displayName": "Recoup Skills", "source": ".", - "version": "1.0.1", - "description": "The open skills library \u2014 portable music-industry skills (artist management, songwriting, analytics, release campaigns, setup) you can drop into any agent. Also installable directly via `npx skills add recoupable/skills`.", + "version": "2.0.0", + "description": "Every Recoup skill in one install — a record label in a box: artist setup & API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release campaigns, plus Recoup's internal engineering & ops tooling. Portable across agents; also installable via `npx skills add recoupable/skills`.", "author": { "name": "Recoupable", "email": "agent@recoupable.com", @@ -20,61 +20,25 @@ "homepage": "https://recoupable.com", "repository": "https://github.com/recoupable/skills", "license": "AGPL-3.0-only", - "keywords": [ - "music", - "artist-management", - "songwriting", - "music-marketing", - "analytics", - "release-management", - "agent-skills" - ], - "interface": { - "displayName": "Recoup Skills", - "shortDescription": "Music industry skills for AI agents", - "longDescription": "Teach your coding agent how to manage artists, write songs, analyze music analytics, plan releases, and more. General skills work for anyone \u2014 platform skills integrate with the Recoupable platform.", - "developerName": "Recoupable", - "category": "Music", - "capabilities": [ - "Read", - "Write" - ], - "websiteURL": "https://recoupable.com", - "brandColor": "#345A5D", - "composerIcon": "./assets/recoup-icon.png", - "logo": "./assets/recoup-icon.png" - } - }, - { - "name": "recoup-records", - "displayName": "Recoup Records", - "source": "./plugins/recoup-records", - "version": "0.1.1", - "description": "A record label in a box \u2014 one install that bundles every Recoup plugin: setup, research, deals, content, song analysis, and releases. Install this instead of picking individual plugins.", - "author": { - "name": "Recoup", - "email": "agent@recoupable.com", - "url": "https://recoupable.com" - }, - "homepage": "https://recoupable.com", - "repository": "https://github.com/recoupable/skills", - "license": "AGPL-3.0-only", "keywords": [ "music", "recoup", "record-label", "artist-management", + "songwriting", "music-research", "catalog-deals", "content-creation", "release-management", + "music-marketing", + "analytics", "agent-skills" ], "interface": { - "displayName": "Recoup Records", - "shortDescription": "A record label in a box", - "longDescription": "The whole Recoup platform as a single plugin: artist setup and API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release workflows \u2014 every skill, agent, and hook in one bundle.", - "developerName": "Recoup", + "displayName": "Recoup Skills", + "shortDescription": "A record label in a box — every Recoup skill in one install", + "longDescription": "The whole Recoup platform as a single plugin: artist setup & API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release campaigns — every skill, agent, and hook in one bundle. Also ships Recoup's internal engineering & ops skills (gated behind the recoup-internal trigger keyword). General skills work for anyone; platform skills integrate with the Recoupable platform.", + "developerName": "Recoupable", "category": "Music", "capabilities": [ "Read", @@ -85,50 +49,6 @@ "composerIcon": "./assets/recoup-icon.png", "logo": "./assets/recoup-icon.png" } - }, - { - "name": "recoup-internal", - "displayName": "Recoup Internal", - "source": "./plugins/recoup-internal", - "version": "0.1.1", - "description": "Recoup's internal toolkit for AI agents \u2014 engineering and operations. Write and maintain high-signal GitHub tracking issues, deliver them docs-first and test-driven end-to-end, benchmark any plugin or skills folder against the frontier of skill design, work the catalog-valuation sales funnel in Attio, produce account-health snapshots for any Recoup account, and draft, publish, and measure data-grounded social posts.", - "author": { - "name": "Recoup", - "email": "agent@recoupable.com", - "url": "https://recoupable.com" - }, - "homepage": "https://recoupable.com", - "repository": "https://github.com/recoupable/skills", - "license": "AGPL-3.0-only", - "keywords": [ - "recoup", - "recoup-internal", - "engineering", - "github-issues", - "issue-tracking", - "test-driven-development", - "skill-design", - "plugin-benchmark", - "sales-pipeline", - "account-health", - "social-posts", - "agent-skills" - ], - "interface": { - "displayName": "Recoup Internal", - "shortDescription": "Recoup's internal engineering + ops skills: issues, skill benchmarks, sales pipeline, account health", - "longDescription": "Recoup's internal toolkit for AI agents. Write and maintain high-signal GitHub tracking issues in the Recoupable house style and deliver them docs-first and test-driven with preview verification, benchmark any plugin or skills folder against the frontier of skill design, work the catalog-valuation sales funnel in Attio from lead to Pro signup, produce account-health snapshots for any Recoup account, and draft, publish, and measure data-grounded social posts.", - "developerName": "Recoup", - "category": "Developer Tools", - "capabilities": [ - "Read", - "Write" - ], - "websiteURL": "https://recoupable.com", - "brandColor": "#345A5D", - "composerIcon": "./assets/recoup-icon.png", - "logo": "./assets/recoup-icon.png" - } } ] } diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 6ae76a4..7d18ed1 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,8 +10,8 @@ "name": "recoup-skills", "displayName": "Recoup Skills", "source": ".", - "description": "The open skills library \u2014 portable music-industry skills (artist management, songwriting, analytics, release campaigns, setup) you can drop into any agent. Also installable directly via `npx skills add recoupable/skills`.", - "version": "1.0.1", + "description": "Every Recoup skill in one install — a record label in a box: artist setup & API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release campaigns, plus Recoup's internal engineering & ops tooling. Portable across agents; also installable via `npx skills add recoupable/skills`.", + "version": "2.0.0", "author": { "name": "Recoupable", "email": "agent@recoupable.com", @@ -20,89 +20,25 @@ "homepage": "https://recoupable.com", "repository": "https://github.com/recoupable/skills", "license": "AGPL-3.0-only", - "keywords": [ - "music", - "artist-management", - "songwriting", - "music-marketing", - "analytics", - "release-management", - "agent-skills" - ], - "category": "Music", - "tags": [ - "skills-library", - "artist-management", - "songwriting" - ] - }, - { - "name": "recoup-records", - "displayName": "Recoup Records", - "source": "./plugins/recoup-records", - "description": "A record label in a box \u2014 one install that bundles every Recoup plugin: setup, research, deals, content, song analysis, and releases. Install this instead of picking individual plugins.", - "version": "0.1.1", - "author": { - "name": "Recoup", - "email": "agent@recoupable.com", - "url": "https://recoupable.com" - }, - "homepage": "https://recoupable.com", - "repository": "https://github.com/recoupable/skills", - "license": "AGPL-3.0-only", "keywords": [ "music", "recoup", "record-label", "artist-management", + "songwriting", "music-research", "catalog-deals", "content-creation", "release-management", + "music-marketing", + "analytics", "agent-skills" ], "category": "Music", "tags": [ "record-label", "all-in-one", - "bundle" - ] - }, - { - "name": "recoup-internal", - "displayName": "Recoup Internal", - "source": "./plugins/recoup-internal", - "description": "Recoup's internal toolkit for AI agents \u2014 engineering and operations. Write and maintain high-signal GitHub tracking issues, deliver them docs-first and test-driven end-to-end, benchmark any plugin or skills folder against the frontier of skill design, work the catalog-valuation sales funnel in Attio, produce account-health snapshots for any Recoup account, and draft, publish, and measure data-grounded social posts.", - "version": "0.1.1", - "author": { - "name": "Recoup", - "email": "agent@recoupable.com", - "url": "https://recoupable.com" - }, - "homepage": "https://recoupable.com", - "repository": "https://github.com/recoupable/skills", - "license": "AGPL-3.0-only", - "keywords": [ - "recoup", - "recoup-internal", - "engineering", - "github-issues", - "issue-tracking", - "test-driven-development", - "skill-design", - "plugin-benchmark", - "sales-pipeline", - "account-health", - "social-posts", - "agent-skills" - ], - "category": "Developer Tools", - "tags": [ - "github-issues", - "test-driven-development", - "skill-design", - "sales-pipeline", - "account-health" + "artist-management" ] } ] diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 3848edb..2a32147 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,8 +1,8 @@ { "name": "recoup-skills", "displayName": "Recoup Skills", - "version": "1.0.1", - "description": "AI agent skills for the music industry — artist management, songwriting, analytics, release campaigns, and more.", + "version": "2.0.0", + "description": "Every Recoup skill in one install — a record label in a box: artist setup & API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release campaigns, plus Recoup's internal engineering & ops tooling. Portable across agents; also installable via `npx skills add recoupable/skills`.", "author": { "name": "Recoupable", "email": "agent@recoupable.com", @@ -13,12 +13,16 @@ "license": "AGPL-3.0-only", "keywords": [ "music", + "recoup", + "record-label", "artist-management", "songwriting", + "music-research", + "catalog-deals", + "content-creation", + "release-management", "music-marketing", "analytics", - "release-management", - "chartmetric", "agent-skills" ] } diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 79c815a..10ed4ae 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "recoup-skills", - "version": "1.0.1", - "description": "AI agent skills for the music industry — artist management, songwriting, analytics, release campaigns, and more.", + "version": "2.0.0", + "description": "Every Recoup skill in one install — a record label in a box: artist setup & API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release campaigns, plus Recoup's internal engineering & ops tooling. Portable across agents; also installable via `npx skills add recoupable/skills`.", "author": { "name": "Recoupable", "email": "agent@recoupable.com", @@ -12,19 +12,23 @@ "license": "AGPL-3.0-only", "keywords": [ "music", + "recoup", + "record-label", "artist-management", "songwriting", + "music-research", + "catalog-deals", + "content-creation", + "release-management", "music-marketing", "analytics", - "release-management", - "chartmetric", "agent-skills" ], "skills": "./skills/", "interface": { "displayName": "Recoup Skills", - "shortDescription": "Music industry skills for AI agents", - "longDescription": "Teach your coding agent how to manage artists, write songs, analyze music analytics, plan releases, and more. General skills work for anyone — platform skills integrate with the Recoupable platform.", + "shortDescription": "A record label in a box — every Recoup skill in one install", + "longDescription": "The whole Recoup platform as a single plugin: artist setup & API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release campaigns — every skill, agent, and hook in one bundle. Also ships Recoup's internal engineering & ops skills (gated behind the recoup-internal trigger keyword). General skills work for anyone; platform skills integrate with the Recoupable platform.", "developerName": "Recoupable", "category": "Productivity", "capabilities": ["Read", "Write"], diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 0524839..7f99b61 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -20,8 +20,8 @@ jobs: run: python3 scripts/check_vendored.py - name: Manifest + parity validation run: python3 scripts/validate_manifests.py - # recoup-records is a first-class, hand-maintained plugin (the focused source - # plugins were consolidated into it), so there is no bundle-drift gate. + # Single flat plugin at the repo root (skills/ + RESOLVER.md); the focused + # source plugins were consolidated into it, so there is no bundle-drift gate. - name: Resolver reachability (no dark skills) run: python3 scripts/check_resolvable.py - name: Resolver eval (routing fixtures + coverage) diff --git a/AGENTS.md b/AGENTS.md index a93fb7e..f66c11d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,34 +8,39 @@ Public skills for AI agents working in the music industry. Skills teach agents h ## Structure -This repo holds **two kinds of thing**: standalone skills and richer plugins. Both are authored here once and install across every harness. +This repo **is a single flat plugin** rooted at the repo root: every skill lives in `skills/`, and the shared components (agents, hooks, references, templates, fixtures, the resolver) sit alongside it. It installs across every harness — as a Claude/agents marketplace plugin, a Codex plugin, or a bare `npx skills add recoupable/skills`. ```text -recoupable/skills/ -├── skills/ ← portable, standalone skills (drop into any agent) -│ ├── chart-metric/ -│ ├── song-writing/ -│ └── ... -├── plugins/ ← rich bundles: skills + hooks + shared references -│ ├── recoup-records/ ← the all-in-one "record label in a box" -│ └── recoup-internal/ ← internal eng + ops (issues, TDD, benchmarks, sales pipeline, account health) +recoupable/skills/ ← the repo root IS the plugin +├── skills/ ← every skill (recoup-[domain]-[verb]-[noun]); recoup-internal-* are staff-gated +│ ├── recoup-roster-add-artist/ +│ ├── recoup-research-the-web/ +│ ├── recoup-internal-dev-issue-tracker/ +│ └── ... (31 skills, flat) +├── agents/ ← specialized subagents +├── hooks/ ← lifecycle hooks (hooks.json + *.sh) +├── references/ ← shared docs (canonical sources for any vendored copies) +├── templates/ ← workspace scaffolds +├── fixtures/ ← golden / demo data +├── RESOLVER.md ← routing table (request → one skill) +├── resolver-eval.jsonl ← routing fixtures ├── scripts/ ← validation gates + vendored.json (shared-file registry) -├── .claude-plugin/ ← repo-as-plugin manifest + marketplace.json (Claude registry) -├── .codex-plugin/ ← repo-as-plugin manifest (Codex) +├── .claude-plugin/ ← plugin.json + marketplace.json (Claude registry) +├── .codex-plugin/ ← plugin.json (Codex) ├── .agents/plugins/ ← marketplace.json (Cursor / agents registry) ├── README.md ├── contributing.md -└── AGENTS.md ← this file +└── AGENTS.md ← this file (CLAUDE.md symlinks here) ``` ## Glossary - **Skill** — a `SKILL.md` folder that teaches one task. Portable; runs on any agent. -- **Plugin** — a bundle in `plugins/{name}/` that ships skills **plus** hooks and shared references, installed through a runtime's plugin system. A skill is a subset of a plugin. (No slash-`commands/` — skills only; see "No slash-commands".) +- **Plugin** — this repo, rooted at the repo root: it ships every skill in `skills/` **plus** agents, hooks, and shared references, installed through a runtime's plugin system or `npx skills`. A skill is a subset of the plugin. (No slash-`commands/` — skills only; see "No slash-commands".) - **Harness** — a runtime that loads skills/plugins: Claude Code, Codex, Cursor, or bare `npx skills`. -- **Marketplace registry** — the list of installable plugins, written in `.claude-plugin/marketplace.json` and `.agents/plugins/marketplace.json`. -- **Canonical / vendored** — when two places need the same file, one copy is the *canonical* source and the rest are byte-identical *vendored* copies tracked in `scripts/vendored.json`. -- **Resolver** — a plugin's `RESOLVER.md` routing table that maps a user request to the one skill that should handle it. Skills stay flat and discoverable; there is no separate "router" entry-point skill. Reachability is enforced by `scripts/check_resolvable.py`. +- **Marketplace registry** — the single installable-plugin entry (`recoup-skills`, `source "."`), written in `.claude-plugin/marketplace.json` and `.agents/plugins/marketplace.json`. +- **Canonical / vendored** — when two places need the same file, one copy is the *canonical* source and the rest are byte-identical *vendored* copies tracked in `scripts/vendored.json` (currently empty — no shared copies today). +- **Resolver** — the repo-root `RESOLVER.md` routing table that maps a user request to the one skill that should handle it. Skills stay flat and discoverable; there is no separate "router" entry-point skill. Reachability is enforced by `scripts/check_resolvable.py`. ## How Skills Load @@ -84,25 +89,14 @@ description: What it does and when to use it - Include **when** to use it — mention trigger phrases users would say - Be specific — vague descriptions won't trigger -## Plugins +## The plugin (this repo) -A plugin lives in `plugins/{name}/` and bundles skills with shared references and per-harness manifests: +The whole repo is one flat plugin. There is **no `plugins/` directory** and no per-plugin subfolders — add capabilities by adding **skills** to `skills/`, not by creating new plugins. -```text -plugins/my-plugin/ -├── .claude-plugin/plugin.json ← manifest for Claude Code -├── .cursor-plugin/plugin.json ← manifest for Cursor -├── .codex-plugin/plugin.json ← manifest for Codex -├── skills/ ← the plugin's skills (same SKILL.md format) -├── references/ ← shared docs, vendored into each skill that needs them -├── README.md -└── LICENSE -``` - -- **Author a plugin by copying an existing one** (e.g. `plugins/recoup-internal/`), then edit the manifests, README, and skills. Don't hand-write manifests from scratch. -- **Ship all three per-plugin manifests** (`.claude-plugin`, `.cursor-plugin`, `.codex-plugin`). They mostly match; only harness-specific fields differ (e.g. Cursor lists a `skills` path). -- **Promote skills into a plugin when they share a canonical reference.** If one skill owns a doc that sibling skills depend on, that cross-dependency breaks the self-contained rule — move them under a plugin and put the shared doc in `plugins/{name}/references/`, vendored into each skill (Portable Skill Contract rule 5). -- Plugin skills follow the **same Portable Skill Contract** as top-level skills. +- **Add a skill** by creating `skills/recoup-[domain]-[verb]-[noun]/SKILL.md` (see "Skill Format"), then add a route in `RESOLVER.md` and a fixture in `resolver-eval.jsonl`. CI fails on any skill that isn't reachable from the resolver. +- **Manifests live at the repo root** — `.claude-plugin/plugin.json` (Claude), `.codex-plugin/plugin.json` (Codex), and the two `marketplace.json` files. Skills auto-resolve from `skills/`; you don't enumerate them in the manifest. +- **Shared components sit at the root** too: `agents/`, `hooks/`, `references/`, `templates/`, `fixtures/`. A skill must still be **self-contained** — it may reference only files inside its own directory, so a skill that needs a shared reference vendors its own copy (Portable Skill Contract rule 5). The root `references/` is the canonical source for any such copies and is also used by `agents/` and `hooks/`. +- Every skill follows the **Portable Skill Contract** below. ## No slash-commands — skills only @@ -136,20 +130,20 @@ What to do instead: ## The marketplace registry -Installable plugins are listed in **two files that must stay identical in content**: +The single plugin (`recoup-skills`, `source "."`) is listed in **two files that must stay identical in content**: - `.claude-plugin/marketplace.json` (Claude) - `.agents/plugins/marketplace.json` (Cursor / agents) -`scripts/validate_manifests.py` enforces this "dual-manifest parity" — **edit one, edit the other**. When you add a plugin, add its entry to both. +`scripts/validate_manifests.py` enforces this "dual-manifest parity" on `(name, source, version)` — **edit one, edit the other**. Keep the version in both marketplaces and both `plugin.json` files in sync. -**Author email must match across layers:** a plugin's email in the marketplace entry must equal the email in that plugin's own `plugin.json`. Use `agent@recoupable.com` everywhere (the support email documented in `CLAUDE.md`). +**Author email must match across layers:** the marketplace entry's email must equal the email in `plugin.json`. Use `agent@recoupable.com` everywhere (the support email documented in `CLAUDE.md`). ## Naming & branding - Slugs are **plain-English, lowercase, hyphenated** — no filler suffixes: `recoup-song-find-hook`, not `recoup-song-find-hook-finder`. -- **Plugin names drop any `-plugin` suffix**: `recoup-records`, not `recoup-records-plugin`. -- Pattern: `recoup-[domain]-[verb]-[noun]` — four words, domain-first, so the `/` list auto-clusters by domain and every name says what it does. Domains are a small, stable set (`platform`, `roster`, `research`, `song`, `content`, `release`, `catalog`); a skill slots into an existing one. A plugin routes to its skills through its `RESOLVER.md` table — no separate router skill. +- **The plugin name drops any `-plugin` suffix**: `recoup-skills`, not `recoup-skills-plugin`. +- Pattern: `recoup-[domain]-[verb]-[noun]` — four words, domain-first, so the `/` list auto-clusters by domain and every name says what it does. Domains are a small, stable set (`platform`, `roster`, `research`, `song`, `content`, `release`, `catalog`); a skill slots into an existing one. **Internal staff skills** add an `internal` segment — `recoup-internal-[domain]-[verb]-[noun]` (five tokens) — and are gated behind the literal `recoup-internal` trigger keyword in their `description`. The plugin routes to its skills through the root `RESOLVER.md` table — no separate router skill. - Preserve history on renames/moves with `git mv`, then update the `name:` frontmatter, cross-references, README tables, and `scripts/vendored.json` paths. ## Portable Skill Contract (cross-harness) @@ -176,7 +170,7 @@ python3 scripts/check_resolvable.py # every skill reachable from RESOLVER python3 scripts/run_resolver_eval.py # routing fixtures valid + full coverage ``` -**`recoup-records` is the flagship, hand-maintained plugin** — "a record label in a box" (`plugins/recoup-records/`) that ships the full platform in one install: artist setup and API access, research, catalog deals, content, song analysis, and releases — every skill, agent, hook, reference, script, template, and fixture. It is self-contained; edit its skills directly. +**This repo is the flagship, hand-maintained plugin** — "a record label in a box" rooted at the repo root that ships the full platform in one install: artist setup and API access, research, catalog deals, content, song analysis, and releases — every skill (in `skills/`), agent, hook, reference, script, template, and fixture. It also ships Recoup's internal eng/ops skills (`recoup-internal-*`, staff-gated behind the `recoup-internal` keyword). Edit the skills directly. **Editing a shared (vendored) file:** change the *canonical* copy only, then re-sync every copy listed in `scripts/vendored.json` (there is no `--sync` flag — copy them yourself), then re-check. Groups come in two shapes: single files (`canonical`/`copies`) and whole directories (`canonical_dir`/`copies_dirs`): diff --git a/README.md b/README.md index e37752c..d5d0f83 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,20 @@ # Recoup Skills -AI agent skills for the music industry. Teach your coding agent how to manage artists, write songs, analyze analytics, plan releases, and more. +AI agent skills for the music industry — a record label in a box. One install gives your agent the whole Recoup platform: artist setup & API access, research, catalog deals, content, song analysis, and releases. ## Install -### Claude Code +### Claude Code / agents marketplace ```bash /plugin marketplace add recoupable/skills +/plugin install recoup-skills@recoup +``` + +### npx (any harness) + +```bash +npx skills add recoupable/skills ``` ### Manual @@ -18,35 +25,94 @@ Clone and point your agent at the `skills/` directory: git clone https://github.com/recoupable/skills.git ``` +> **One plugin, everything included.** As of v2 this repo ships as a **single flat plugin** rooted at the repo root — there are no separate "records" or "internal" installs anymore. Adding the marketplace (or `npx skills add recoupable/skills`) installs every skill in `skills/`. The agent picks the right one via [`RESOLVER.md`](RESOLVER.md). + ## Skills +Every skill is named `recoup-[domain]-[verb]-[noun]`, so the `/` list clusters by domain. + +### roster — your artists + | Skill | What it does | |-------|-------------| -| [streaming-growth](skills/streaming-growth) | Grow a new artist past streaming milestones that unlock platform tools | -| [brand-guidelines](skills/brand-guidelines) | Apply Recoupable's brand identity to agent outputs | -| [chartmetric](skills/chartmetric) | Query and analyze music data from the Chartmetric API | -| [content-creation](skills/content-creation) | Create social videos, TikToks, Reels, and visual content using AI primitives | -| [music-industry-research](skills/music-industry-research) | Music industry research — artist analytics, people search, competitive analysis, web intelligence | -| [release-management](skills/release-management) | Plan and execute release campaigns | -| [social-posts](skills/social-posts) | Write data-grounded LinkedIn and X posts for announcements and artist highlights, publish via the connector, and track performance | -| [songwriting](skills/songwriting) | Structured songwriting using the 7 C's method | -| [trend-to-song](skills/trend-to-song) | Turn cultural moments into songs and campaign strategies | +| recoup-roster-add-artist | Onboard & enrich a new artist (the 8-call setup chain) | +| recoup-roster-list-artists | See who's on your roster | +| recoup-roster-manage-artist | Work inside one artist's folder — context, brand, songs, releases | -> **Setup, API access, and artist onboarding** now ship inside the **Recoup Records** plugin below. +### research — intelligence -## Plugins +| Skill | What it does | +|-------|-------------| +| recoup-research-artist-overview | Full research sweep on one artist | +| recoup-research-find-talent | A&R scouting for emerging/unsigned artists | +| recoup-research-playlist-targets | Catalog-wide playlist strategy & placement gaps | +| recoup-research-find-contacts | Find managers/A&R/press + draft outreach | +| recoup-research-weekly-brief | The recurring "what changed this week" update | +| recoup-research-the-web | Open-web search, deep research, entity enrichment | -Beyond the open skills library above, Recoup ships two **plugins** — richer bundles of skills, agents, and hooks. Add the marketplace once, then install what you need: +### song — single-song audio -```bash -/plugin marketplace add recoupable/skills -/plugin install recoup-records@recoup -``` +| Skill | What it does | +|-------|-------------| +| recoup-song-analyze-audio | Understand a song from its audio (BPM/key/genre, lyrics, mix) | +| recoup-song-find-hook | Find the most clip-worthy 5–15 seconds | +| recoup-song-placement-pitch | Playlist/editorial pitch + sync brief from the audio | + +### content — assets + +| Skill | What it does | +|-------|-------------| +| recoup-content-write-caption | Captions in the artist's own voice | +| recoup-content-make-graphics | Cover art, thumbnails, carousels, promo/quote cards | +| recoup-content-make-video | Short-form video, lyric videos, visualizers, reformats | +| recoup-content-asset-pack | A whole 15–30-piece clip family for one song | +| recoup-content-reactive-post | Turn a real milestone/trend into a timely post | + +### release — release workflow + +| Skill | What it does | +|-------|-------------| +| recoup-release-plan-rollout | Plan & run a release end to end | +| recoup-release-track-drop | Confirm a drop & build a launch-day alert | + +### catalog — catalog deals + +| Skill | What it does | +|-------|-------------| +| recoup-catalog-review-deal | Underwrite a deal end to end (data room → IC memo) | +| recoup-catalog-estimate-value | Value a catalog from public data alone (no seller files) | + +### platform — operate the system + +| Skill | What it does | +|-------|-------------| +| recoup-platform-connect-account | First-run setup: verify email, mint an API key | +| recoup-platform-build-workspace | Scaffold your orgs/artists folder tree | +| recoup-platform-api-access | Call the Recoup API & external connectors directly | +| recoup-platform-capture-lesson | Capture a reusable lesson (compounding memory) | + +### internal — Recoup staff only (gated by the `recoup-internal` keyword) + +> OFF by default. These fire **only** when the request explicitly includes `recoup-internal`. They operate Recoup's own engineering and go-to-market systems — never customer-facing work. + +| Skill | What it does | +|-------|-------------| +| recoup-internal-dev-issue-tracker | Write & maintain high-signal GitHub tracking issues | +| recoup-internal-dev-ship-issue | Deliver a tracked issue end-to-end (docs-first, TDD) | +| recoup-internal-eval-skill-benchmark | Benchmark a skill pack/plugin against the frontier | +| recoup-internal-funnel-valuation-pipeline | Work the Attio catalog-valuation sales funnel | +| recoup-internal-account-health-report | Account-health snapshot for any Recoup account | +| recoup-internal-social-ship-posts | Draft, publish & measure LinkedIn/X posts | + +## What else ships + +Beyond the skills, the plugin bundles shared components at the repo root: -| Plugin | What it does | -|--------|-------------| -| [Recoup Records](plugins/recoup-records) | A record label in a box — the whole platform in one install: artist setup & API access, research, catalog deals, content, song analysis, and releases | -| [Recoup Internal](plugins/recoup-internal) | Recoup's internal engineering & ops — write and ship high-signal GitHub tracking issues docs-first and test-driven, benchmark skill packs against the frontier, work the catalog-valuation sales funnel in Attio, and produce account-health snapshots | +- **`agents/`** — specialized subagents (deal QC, market scout, royalty audit, rights chain, valuation sensitivity, metadata reconciler, release readiness, research analyst) +- **`hooks/`** — completion-gate and environment-check hooks +- **`references/`, `templates/`, `fixtures/`** — shared docs, workspace scaffolds, and golden/demo data +- **`RESOLVER.md`** — the routing table the agent uses to pick a skill +- **`scripts/`** — repo validators (portability, vendoring, manifests, resolver reachability + eval) ## Creating a Skill @@ -66,7 +132,7 @@ skills/ └── fixtures/ ← optional — sample / golden data ``` -See [contributing.md](contributing.md) for guidelines. +Add a route for the new skill in [`RESOLVER.md`](RESOLVER.md) and a fixture in `resolver-eval.jsonl` — CI fails on unreachable skills. See [contributing.md](contributing.md) for guidelines. ## About diff --git a/plugins/recoup-records/RESOLVER.md b/RESOLVER.md similarity index 75% rename from plugins/recoup-records/RESOLVER.md rename to RESOLVER.md index 966f049..d052cc8 100644 --- a/plugins/recoup-records/RESOLVER.md +++ b/RESOLVER.md @@ -1,9 +1,10 @@ -# RESOLVER — recoup-records skill dispatcher (middle-tier · domain-grouped) +# RESOLVER — Recoup Skills dispatcher (domain-grouped) -> The bundle's routing table. Skills are grouped by **domain** in two tiers: -> **capability** domains (the label work) and a **platform** domain (operating the -> system). Every name is `recoup-[domain]-[verb]-[noun]` — the domain prefix tells -> you the room, the verb+noun tail tells you the job. +> The plugin's routing table for every skill in `skills/`. Capability and platform +> skills are named `recoup-[domain]-[verb]-[noun]`; internal staff skills are named +> `recoup-internal-[domain]-[verb]-[noun]` and are gated behind the `recoup-internal` +> keyword (Tier 3). The domain prefix tells you the room, the verb+noun tail tells +> you the job. > > **Read the matched skill's `SKILL.md` before acting.** Verified by > `scripts/check_resolvable.py` (every skill has a route; every route resolves) and @@ -64,6 +65,21 @@ | call the Recoup API directly, fetch a resource, run a connector (Docs/Gmail/TikTok) | `recoup-platform-api-access` | | "remember this" / capture a reusable lesson | `recoup-platform-capture-lesson` | +## Tier 3 — Internal (Recoup staff only · gated by the `recoup-internal` keyword) + +> OFF by default. Route here **only** when the request explicitly contains the +> literal keyword `recoup-internal`. Never use these for customer-facing or artist +> work — they operate Recoup's own engineering and go-to-market systems. + +| Intent | Skill | +|---|---| +| "recoup-internal" + write/maintain a GitHub tracking issue | `recoup-internal-dev-issue-tracker` | +| "recoup-internal" + implement/ship a tracked issue end-to-end (docs-first, TDD) | `recoup-internal-dev-ship-issue` | +| "recoup-internal" + benchmark/audit/grade a skill pack or plugin | `recoup-internal-eval-skill-benchmark` | +| "recoup-internal" + work the Attio catalog-valuation sales funnel | `recoup-internal-funnel-valuation-pipeline` | +| "recoup-internal" + account-health / account-status snapshot for a Recoup account | `recoup-internal-account-health-report` | +| "recoup-internal" + draft/ship/measure LinkedIn or X posts | `recoup-internal-social-ship-posts` | + --- ## Cross-skill disambiguation (the boundaries that matter) diff --git a/plugins/recoup-records/agents/deal-qc-reviewer.md b/agents/deal-qc-reviewer.md similarity index 100% rename from plugins/recoup-records/agents/deal-qc-reviewer.md rename to agents/deal-qc-reviewer.md diff --git a/plugins/recoup-records/agents/market-scout.md b/agents/market-scout.md similarity index 100% rename from plugins/recoup-records/agents/market-scout.md rename to agents/market-scout.md diff --git a/plugins/recoup-records/agents/metadata-reconciler.md b/agents/metadata-reconciler.md similarity index 100% rename from plugins/recoup-records/agents/metadata-reconciler.md rename to agents/metadata-reconciler.md diff --git a/plugins/recoup-records/agents/release-readiness-reviewer.md b/agents/release-readiness-reviewer.md similarity index 100% rename from plugins/recoup-records/agents/release-readiness-reviewer.md rename to agents/release-readiness-reviewer.md diff --git a/plugins/recoup-records/agents/research-analyst.md b/agents/research-analyst.md similarity index 100% rename from plugins/recoup-records/agents/research-analyst.md rename to agents/research-analyst.md diff --git a/plugins/recoup-records/agents/rights-chain-reviewer.md b/agents/rights-chain-reviewer.md similarity index 100% rename from plugins/recoup-records/agents/rights-chain-reviewer.md rename to agents/rights-chain-reviewer.md diff --git a/plugins/recoup-records/agents/royalty-audit-analyst.md b/agents/royalty-audit-analyst.md similarity index 100% rename from plugins/recoup-records/agents/royalty-audit-analyst.md rename to agents/royalty-audit-analyst.md diff --git a/plugins/recoup-records/agents/valuation-sensitivity-analyst.md b/agents/valuation-sensitivity-analyst.md similarity index 100% rename from plugins/recoup-records/agents/valuation-sensitivity-analyst.md rename to agents/valuation-sensitivity-analyst.md diff --git a/plugins/recoup-records/fixtures/demo-data-room/source/00_overview/README.md b/fixtures/demo-data-room/source/00_overview/README.md similarity index 100% rename from plugins/recoup-records/fixtures/demo-data-room/source/00_overview/README.md rename to fixtures/demo-data-room/source/00_overview/README.md diff --git a/plugins/recoup-records/fixtures/demo-data-room/source/01_track_list/track-list.csv b/fixtures/demo-data-room/source/01_track_list/track-list.csv similarity index 100% rename from plugins/recoup-records/fixtures/demo-data-room/source/01_track_list/track-list.csv rename to fixtures/demo-data-room/source/01_track_list/track-list.csv diff --git a/plugins/recoup-records/fixtures/demo-data-room/source/02_compositions/composition-list.csv b/fixtures/demo-data-room/source/02_compositions/composition-list.csv similarity index 100% rename from plugins/recoup-records/fixtures/demo-data-room/source/02_compositions/composition-list.csv rename to fixtures/demo-data-room/source/02_compositions/composition-list.csv diff --git a/plugins/recoup-records/fixtures/demo-data-room/source/03_royalty_statements/ascap/ascap-statements-2024.csv b/fixtures/demo-data-room/source/03_royalty_statements/ascap/ascap-statements-2024.csv similarity index 100% rename from plugins/recoup-records/fixtures/demo-data-room/source/03_royalty_statements/ascap/ascap-statements-2024.csv rename to fixtures/demo-data-room/source/03_royalty_statements/ascap/ascap-statements-2024.csv diff --git a/plugins/recoup-records/fixtures/demo-data-room/source/03_royalty_statements/distributor/distributor-2024.csv b/fixtures/demo-data-room/source/03_royalty_statements/distributor/distributor-2024.csv similarity index 100% rename from plugins/recoup-records/fixtures/demo-data-room/source/03_royalty_statements/distributor/distributor-2024.csv rename to fixtures/demo-data-room/source/03_royalty_statements/distributor/distributor-2024.csv diff --git a/plugins/recoup-records/fixtures/demo-data-room/source/03_royalty_statements/mlc/mlc-2024.csv b/fixtures/demo-data-room/source/03_royalty_statements/mlc/mlc-2024.csv similarity index 100% rename from plugins/recoup-records/fixtures/demo-data-room/source/03_royalty_statements/mlc/mlc-2024.csv rename to fixtures/demo-data-room/source/03_royalty_statements/mlc/mlc-2024.csv diff --git a/plugins/recoup-records/fixtures/demo-data-room/source/03_royalty_statements/soundexchange/soundexchange-2024.csv b/fixtures/demo-data-room/source/03_royalty_statements/soundexchange/soundexchange-2024.csv similarity index 100% rename from plugins/recoup-records/fixtures/demo-data-room/source/03_royalty_statements/soundexchange/soundexchange-2024.csv rename to fixtures/demo-data-room/source/03_royalty_statements/soundexchange/soundexchange-2024.csv diff --git a/plugins/recoup-records/fixtures/demo-data-room/source/03_royalty_statements/youtube/youtube-cid-2024.csv b/fixtures/demo-data-room/source/03_royalty_statements/youtube/youtube-cid-2024.csv similarity index 100% rename from plugins/recoup-records/fixtures/demo-data-room/source/03_royalty_statements/youtube/youtube-cid-2024.csv rename to fixtures/demo-data-room/source/03_royalty_statements/youtube/youtube-cid-2024.csv diff --git a/plugins/recoup-records/fixtures/demo-data-room/source/04_rights_documents/agreements/publishing-admin-agreement-2019.txt b/fixtures/demo-data-room/source/04_rights_documents/agreements/publishing-admin-agreement-2019.txt similarity index 100% rename from plugins/recoup-records/fixtures/demo-data-room/source/04_rights_documents/agreements/publishing-admin-agreement-2019.txt rename to fixtures/demo-data-room/source/04_rights_documents/agreements/publishing-admin-agreement-2019.txt diff --git a/plugins/recoup-records/fixtures/demo-data-room/source/04_rights_documents/agreements/recording-agreement-2018.txt b/fixtures/demo-data-room/source/04_rights_documents/agreements/recording-agreement-2018.txt similarity index 100% rename from plugins/recoup-records/fixtures/demo-data-room/source/04_rights_documents/agreements/recording-agreement-2018.txt rename to fixtures/demo-data-room/source/04_rights_documents/agreements/recording-agreement-2018.txt diff --git a/plugins/recoup-records/fixtures/demo-data-room/source/04_rights_documents/split_sheets/bright-lights-split.txt b/fixtures/demo-data-room/source/04_rights_documents/split_sheets/bright-lights-split.txt similarity index 100% rename from plugins/recoup-records/fixtures/demo-data-room/source/04_rights_documents/split_sheets/bright-lights-split.txt rename to fixtures/demo-data-room/source/04_rights_documents/split_sheets/bright-lights-split.txt diff --git a/plugins/recoup-records/fixtures/demo-release/assumptions.yaml b/fixtures/demo-release/assumptions.yaml similarity index 100% rename from plugins/recoup-records/fixtures/demo-release/assumptions.yaml rename to fixtures/demo-release/assumptions.yaml diff --git a/plugins/recoup-records/fixtures/external-sources.md b/fixtures/external-sources.md similarity index 100% rename from plugins/recoup-records/fixtures/external-sources.md rename to fixtures/external-sources.md diff --git a/plugins/recoup-records/fixtures/golden/apra-pdf/apra-2024.pdf b/fixtures/golden/apra-pdf/apra-2024.pdf similarity index 100% rename from plugins/recoup-records/fixtures/golden/apra-pdf/apra-2024.pdf rename to fixtures/golden/apra-pdf/apra-2024.pdf diff --git a/plugins/recoup-records/fixtures/golden/apra-pdf/expected-royalty-ledger.csv b/fixtures/golden/apra-pdf/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/apra-pdf/expected-royalty-ledger.csv rename to fixtures/golden/apra-pdf/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/ascap-pdf/ascap-2024Q1.pdf b/fixtures/golden/ascap-pdf/ascap-2024Q1.pdf similarity index 100% rename from plugins/recoup-records/fixtures/golden/ascap-pdf/ascap-2024Q1.pdf rename to fixtures/golden/ascap-pdf/ascap-2024Q1.pdf diff --git a/plugins/recoup-records/fixtures/golden/ascap-pdf/expected-royalty-ledger.csv b/fixtures/golden/ascap-pdf/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/ascap-pdf/expected-royalty-ledger.csv rename to fixtures/golden/ascap-pdf/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/ascap-performance/expected-royalty-ledger.csv b/fixtures/golden/ascap-performance/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/ascap-performance/expected-royalty-ledger.csv rename to fixtures/golden/ascap-performance/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/ascap-performance/input.csv b/fixtures/golden/ascap-performance/input.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/ascap-performance/input.csv rename to fixtures/golden/ascap-performance/input.csv diff --git a/plugins/recoup-records/fixtures/golden/bmi-performance/expected-royalty-ledger.csv b/fixtures/golden/bmi-performance/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/bmi-performance/expected-royalty-ledger.csv rename to fixtures/golden/bmi-performance/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/bmi-performance/input.csv b/fixtures/golden/bmi-performance/input.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/bmi-performance/input.csv rename to fixtures/golden/bmi-performance/input.csv diff --git a/plugins/recoup-records/fixtures/golden/bmi-real-headers/expected-royalty-ledger.csv b/fixtures/golden/bmi-real-headers/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/bmi-real-headers/expected-royalty-ledger.csv rename to fixtures/golden/bmi-real-headers/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/bmi-real-headers/expected-status.json b/fixtures/golden/bmi-real-headers/expected-status.json similarity index 100% rename from plugins/recoup-records/fixtures/golden/bmi-real-headers/expected-status.json rename to fixtures/golden/bmi-real-headers/expected-status.json diff --git a/plugins/recoup-records/fixtures/golden/bmi-real-headers/input.csv b/fixtures/golden/bmi-real-headers/input.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/bmi-real-headers/input.csv rename to fixtures/golden/bmi-real-headers/input.csv diff --git a/plugins/recoup-records/fixtures/golden/curve-income/expected-royalty-ledger.csv b/fixtures/golden/curve-income/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/curve-income/expected-royalty-ledger.csv rename to fixtures/golden/curve-income/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/curve-income/input.csv b/fixtures/golden/curve-income/input.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/curve-income/input.csv rename to fixtures/golden/curve-income/input.csv diff --git a/plugins/recoup-records/fixtures/golden/direct-sync/expected-royalty-ledger.csv b/fixtures/golden/direct-sync/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/direct-sync/expected-royalty-ledger.csv rename to fixtures/golden/direct-sync/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/direct-sync/input.csv b/fixtures/golden/direct-sync/input.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/direct-sync/input.csv rename to fixtures/golden/direct-sync/input.csv diff --git a/plugins/recoup-records/fixtures/golden/distributor-master/expected-royalty-ledger.csv b/fixtures/golden/distributor-master/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/distributor-master/expected-royalty-ledger.csv rename to fixtures/golden/distributor-master/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/distributor-master/input.csv b/fixtures/golden/distributor-master/input.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/distributor-master/input.csv rename to fixtures/golden/distributor-master/input.csv diff --git a/plugins/recoup-records/fixtures/golden/generate-pdf-fixtures.py b/fixtures/golden/generate-pdf-fixtures.py similarity index 100% rename from plugins/recoup-records/fixtures/golden/generate-pdf-fixtures.py rename to fixtures/golden/generate-pdf-fixtures.py diff --git a/plugins/recoup-records/fixtures/golden/hfa-pdf/expected-royalty-ledger.csv b/fixtures/golden/hfa-pdf/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/hfa-pdf/expected-royalty-ledger.csv rename to fixtures/golden/hfa-pdf/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/hfa-pdf/hfa-statement.pdf b/fixtures/golden/hfa-pdf/hfa-statement.pdf similarity index 100% rename from plugins/recoup-records/fixtures/golden/hfa-pdf/hfa-statement.pdf rename to fixtures/golden/hfa-pdf/hfa-statement.pdf diff --git a/plugins/recoup-records/fixtures/golden/jasrac-pdf/expected-royalty-ledger.csv b/fixtures/golden/jasrac-pdf/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/jasrac-pdf/expected-royalty-ledger.csv rename to fixtures/golden/jasrac-pdf/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/jasrac-pdf/jasrac-2024.pdf b/fixtures/golden/jasrac-pdf/jasrac-2024.pdf similarity index 100% rename from plugins/recoup-records/fixtures/golden/jasrac-pdf/jasrac-2024.pdf rename to fixtures/golden/jasrac-pdf/jasrac-2024.pdf diff --git a/plugins/recoup-records/fixtures/golden/mlc-mechanical/expected-royalty-ledger.csv b/fixtures/golden/mlc-mechanical/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/mlc-mechanical/expected-royalty-ledger.csv rename to fixtures/golden/mlc-mechanical/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/mlc-mechanical/input.csv b/fixtures/golden/mlc-mechanical/input.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/mlc-mechanical/input.csv rename to fixtures/golden/mlc-mechanical/input.csv diff --git a/plugins/recoup-records/fixtures/golden/prs-pdf/expected-royalty-ledger.csv b/fixtures/golden/prs-pdf/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/prs-pdf/expected-royalty-ledger.csv rename to fixtures/golden/prs-pdf/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/prs-pdf/prs-2024.pdf b/fixtures/golden/prs-pdf/prs-2024.pdf similarity index 100% rename from plugins/recoup-records/fixtures/golden/prs-pdf/prs-2024.pdf rename to fixtures/golden/prs-pdf/prs-2024.pdf diff --git a/plugins/recoup-records/fixtures/golden/publisher-admin/expected-royalty-ledger.csv b/fixtures/golden/publisher-admin/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/publisher-admin/expected-royalty-ledger.csv rename to fixtures/golden/publisher-admin/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/publisher-admin/input.csv b/fixtures/golden/publisher-admin/input.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/publisher-admin/input.csv rename to fixtures/golden/publisher-admin/input.csv diff --git a/plugins/recoup-records/fixtures/golden/sacem-pdf/expected-royalty-ledger.csv b/fixtures/golden/sacem-pdf/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/sacem-pdf/expected-royalty-ledger.csv rename to fixtures/golden/sacem-pdf/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/sacem-pdf/sacem-2024Q2.pdf b/fixtures/golden/sacem-pdf/sacem-2024Q2.pdf similarity index 100% rename from plugins/recoup-records/fixtures/golden/sacem-pdf/sacem-2024Q2.pdf rename to fixtures/golden/sacem-pdf/sacem-2024Q2.pdf diff --git a/plugins/recoup-records/fixtures/golden/sadaic-pdf/expected-royalty-ledger.csv b/fixtures/golden/sadaic-pdf/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/sadaic-pdf/expected-royalty-ledger.csv rename to fixtures/golden/sadaic-pdf/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/sadaic-pdf/sadaic-2024.pdf b/fixtures/golden/sadaic-pdf/sadaic-2024.pdf similarity index 100% rename from plugins/recoup-records/fixtures/golden/sadaic-pdf/sadaic-2024.pdf rename to fixtures/golden/sadaic-pdf/sadaic-2024.pdf diff --git a/plugins/recoup-records/fixtures/golden/socan-pdf/expected-royalty-ledger.csv b/fixtures/golden/socan-pdf/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/socan-pdf/expected-royalty-ledger.csv rename to fixtures/golden/socan-pdf/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/socan-pdf/socan-2024.pdf b/fixtures/golden/socan-pdf/socan-2024.pdf similarity index 100% rename from plugins/recoup-records/fixtures/golden/socan-pdf/socan-2024.pdf rename to fixtures/golden/socan-pdf/socan-2024.pdf diff --git a/plugins/recoup-records/fixtures/golden/soundexchange-pdf/expected-royalty-ledger.csv b/fixtures/golden/soundexchange-pdf/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/soundexchange-pdf/expected-royalty-ledger.csv rename to fixtures/golden/soundexchange-pdf/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/soundexchange-pdf/soundexchange-2024.pdf b/fixtures/golden/soundexchange-pdf/soundexchange-2024.pdf similarity index 100% rename from plugins/recoup-records/fixtures/golden/soundexchange-pdf/soundexchange-2024.pdf rename to fixtures/golden/soundexchange-pdf/soundexchange-2024.pdf diff --git a/plugins/recoup-records/fixtures/golden/soundexchange/expected-royalty-ledger.csv b/fixtures/golden/soundexchange/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/soundexchange/expected-royalty-ledger.csv rename to fixtures/golden/soundexchange/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/soundexchange/input.csv b/fixtures/golden/soundexchange/input.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/soundexchange/input.csv rename to fixtures/golden/soundexchange/input.csv diff --git a/plugins/recoup-records/fixtures/golden/youtube-content-id/expected-royalty-ledger.csv b/fixtures/golden/youtube-content-id/expected-royalty-ledger.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/youtube-content-id/expected-royalty-ledger.csv rename to fixtures/golden/youtube-content-id/expected-royalty-ledger.csv diff --git a/plugins/recoup-records/fixtures/golden/youtube-content-id/input.csv b/fixtures/golden/youtube-content-id/input.csv similarity index 100% rename from plugins/recoup-records/fixtures/golden/youtube-content-id/input.csv rename to fixtures/golden/youtube-content-id/input.csv diff --git a/plugins/recoup-records/hooks/check-env.sh b/hooks/check-env.sh similarity index 100% rename from plugins/recoup-records/hooks/check-env.sh rename to hooks/check-env.sh diff --git a/plugins/recoup-records/hooks/hooks.json b/hooks/hooks.json similarity index 100% rename from plugins/recoup-records/hooks/hooks.json rename to hooks/hooks.json diff --git a/plugins/recoup-records/hooks/protect-source-files.sh b/hooks/protect-source-files.sh similarity index 100% rename from plugins/recoup-records/hooks/protect-source-files.sh rename to hooks/protect-source-files.sh diff --git a/plugins/recoup-internal/.claude-plugin/plugin.json b/plugins/recoup-internal/.claude-plugin/plugin.json deleted file mode 100644 index bd0df2d..0000000 --- a/plugins/recoup-internal/.claude-plugin/plugin.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "recoup-internal", - "displayName": "Recoup Internal", - "version": "0.1.1", - "description": "Recoup's internal toolkit for AI agents — engineering and operations. Write and maintain high-signal GitHub tracking issues, deliver them docs-first and test-driven end-to-end, benchmark any plugin or skills folder against the frontier of skill design, work the catalog-valuation sales funnel in Attio, and produce account-health snapshots for any Recoup account.", - "author": { - "name": "Recoup", - "email": "agent@recoupable.com", - "url": "https://recoupable.com" - }, - "homepage": "https://recoupable.com", - "repository": "https://github.com/recoupable/skills", - "license": "AGPL-3.0-only", - "keywords": [ - "recoup", - "recoup-internal", - "engineering", - "github-issues", - "issue-tracking", - "test-driven-development", - "skill-design", - "plugin-benchmark", - "sales-pipeline", - "account-health", - "agent-skills" - ] -} diff --git a/plugins/recoup-internal/.codex-plugin/plugin.json b/plugins/recoup-internal/.codex-plugin/plugin.json deleted file mode 100644 index 4eacd0e..0000000 --- a/plugins/recoup-internal/.codex-plugin/plugin.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "recoup-internal", - "version": "0.1.1", - "description": "Recoup's internal toolkit for AI agents — engineering and operations. Write and maintain high-signal GitHub tracking issues, deliver them docs-first and test-driven end-to-end, benchmark any plugin or skills folder against the frontier of skill design, work the catalog-valuation sales funnel in Attio, and produce account-health snapshots for any Recoup account.", - "author": { - "name": "Recoup", - "email": "agent@recoupable.com", - "url": "https://recoupable.com" - }, - "homepage": "https://recoupable.com", - "repository": "https://github.com/recoupable/skills", - "license": "AGPL-3.0-only", - "keywords": [ - "recoup", - "recoup-internal", - "engineering", - "github-issues", - "issue-tracking", - "test-driven-development", - "skill-design", - "plugin-benchmark", - "sales-pipeline", - "account-health", - "agent-skills" - ], - "skills": "./skills/", - "interface": { - "displayName": "Recoup Internal", - "shortDescription": "Recoup's internal engineering + ops skills: issues, skill benchmarks, sales pipeline, account health", - "longDescription": "Recoup's internal toolkit for AI agents. Write and maintain high-signal GitHub tracking issues in the Recoupable house style and deliver them docs-first and test-driven with preview verification, benchmark any plugin or skills folder against the frontier of skill design, work the catalog-valuation sales funnel in Attio from lead to Pro signup, and produce account-health snapshots for any Recoup account.", - "developerName": "Recoup", - "category": "Developer Tools", - "capabilities": ["Read", "Write"], - "websiteURL": "https://recoupable.com", - "composerIcon": "./assets/recoup-icon.png", - "logo": "./assets/recoup-icon.png" - } -} diff --git a/plugins/recoup-internal/.cursor-plugin/plugin.json b/plugins/recoup-internal/.cursor-plugin/plugin.json deleted file mode 100644 index 7086465..0000000 --- a/plugins/recoup-internal/.cursor-plugin/plugin.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "recoup-internal", - "version": "0.1.1", - "description": "Recoup's internal toolkit for AI agents — engineering and operations. Write and maintain high-signal GitHub tracking issues, deliver them docs-first and test-driven end-to-end, benchmark any plugin or skills folder against the frontier of skill design, work the catalog-valuation sales funnel in Attio, and produce account-health snapshots for any Recoup account.", - "author": { - "name": "Recoup", - "email": "agent@recoupable.com" - }, - "homepage": "https://recoupable.com", - "repository": "https://github.com/recoupable/skills", - "license": "AGPL-3.0-only", - "logo": "assets/recoup-icon.png", - "keywords": [ - "recoup", - "recoup-internal", - "engineering", - "github-issues", - "issue-tracking", - "test-driven-development", - "skill-design", - "plugin-benchmark", - "sales-pipeline", - "account-health", - "agent-skills" - ], - "skills": "./skills/" -} diff --git a/plugins/recoup-internal/LICENSE b/plugins/recoup-internal/LICENSE deleted file mode 100644 index 2101d55..0000000 --- a/plugins/recoup-internal/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ -This project is licensed under the AGPL-3.0 for non-commercial use. - -Commercial Use - -For commercial use or deployments requiring a setup, please contact us for a -commercial license at support@recoupable.com. By using this software, you agree -to the terms of the license. - ----------------------------------------------------------------------- -The full text of the GNU Affero General Public License, version 3.0, -which governs non-commercial use, follows below. ----------------------------------------------------------------------- - - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/plugins/recoup-internal/README.md b/plugins/recoup-internal/README.md deleted file mode 100644 index caa105e..0000000 --- a/plugins/recoup-internal/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Recoup Internal - -Agent plugin for **Recoup's internal workflows** — engineering and operations. -Write and maintain high-signal GitHub tracking issues, deliver them docs-first -and test-driven from open to shipped, benchmark any plugin or skills folder -against the frontier of skill design, work the catalog-valuation sales funnel in -Attio, produce account-health snapshots for any Recoup account, and draft, ship, -and measure data-grounded social posts. Built by [Recoup](https://recoupable.com). - -These are the internal-facing skills behind the rest of the Recoup skills repo — -the dev-tooling and ops skills the team runs, as opposed to the artist/label -product (see `recoup-records`). - -## Install - -`recoup-internal` ships as a plugin in the **`recoupable/skills`** marketplace — -it is not a standalone repo. Add the marketplace once, then install the plugin. - -### Claude Code (CLI) - -```bash -/plugin marketplace add recoupable/skills -/plugin install recoup-internal@recoup -``` - -### Claude Cowork - -1. Open the plugin marketplace (puzzle-piece icon in the sidebar). -2. Click **Add custom plugin** and paste the marketplace repo: - `https://github.com/recoupable/skills` -3. Install **recoup-internal** from the list and approve the requested tool - permissions (`Read`, `Write`). -4. Confirm: type `/plugin` and check that `recoup-internal` is listed. - -### Cursor - -1. Cursor → Settings → Plugins → **Add custom plugin** and paste - `https://github.com/recoupable/skills`. -2. Install **recoup-internal**, then restart Cursor so its skills load. - -## Skills - -| Skill | What it does | -|-------|-------------| -| [recoup-dev-issue-tracker](skills/recoup-dev-issue-tracker) | Write and maintain high-signal GitHub tracking issues that coordinate multi-PR, multi-repo work — structure, the Open→Done lifecycle, evidence/linking rules, and acceptance criteria | -| [recoup-dev-ship-issue](skills/recoup-dev-ship-issue) | Deliver a tracked issue end-to-end — documentation-driven (contract first), then test-driven (red→green) implementation, verified against the live preview and posted to the PR | -| [recoup-eval-skill-benchmark](skills/recoup-eval-skill-benchmark) | Benchmark any plugin, skill pack, or skills folder against the frontier (gstack, gbrain, compound-engineering, PM OS) — measure it, grade a 15-dimension scorecard, and produce a prioritized list of moves to steal | -| [recoup-funnel-valuation-pipeline](skills/recoup-funnel-valuation-pipeline) | Work the catalog-valuation sales funnel in Attio — qualify an inbound lead against revenue goals, enrich and advance the CRM record, and draft the first outreach email plus a one-page valuation PDF | -| [recoup-account-health-report](skills/recoup-account-health-report) | Produce an account-health snapshot for any Recoup account (artists, socials, chats, tasks, credits, subscription) as an ACCOUNT.md, a per-artist tree, and a polished status PDF — admin token inspects any account, owner token only its own | -| [recoup-social-ship-posts](skills/recoup-social-ship-posts) | Write, publish, and measure data-grounded LinkedIn and X posts — product announcements and artist highlights — with copy principles, CTA choice, connector publishing (incl. X native video), and a learn→draft→publish→re-measure loop | - -## License - -This project is licensed under the AGPL-3.0 for non-commercial use. See [LICENSE](LICENSE). - -### Commercial Use - -For commercial use or deployments requiring a setup, please contact us for a -commercial license at support@recoupable.com. By using this software, you agree -to the terms of the license. diff --git a/plugins/recoup-internal/assets/recoup-icon.png b/plugins/recoup-internal/assets/recoup-icon.png deleted file mode 100644 index 89d1b75c154a1d7edb93a9e7601afd72ee0765cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129041 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�r;tfbg#JX&RnK*H?70Asb}8T)WC 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 Caveat the frontier itself flags: even gstack strains "thin" — its root -> `SKILL.md` runs a ~100-line bash preamble before any skill logic -> ("infrastructure as preamble"). Lesson: don't confuse "fat skill" with "heavy -> boilerplate on every call." - ---- - -## 3. latent vs. deterministic — the triage discipline - -Every step is either **latent** (needs judgment: read, interpret, decide, -synthesize) or **deterministic** (same input → same output: SQL, math, sorting, -date conversion). Put each on the side it belongs. - -> "The most common mistake in agent design isn't a wrong answer — it's a wrong -> **side**." Deterministic work done in latent space gets plausibly, -> confidently wrong. - -The load-bearing move is a **loop**: the model *writes* the deterministic tool, -then the tool *constrains* the model (gstack's `/scrape` graduates into a -deterministic `script.ts` + `script.test.ts`; the next run is ~200ms instead of -~30s). Stated as an authoring principle: **"skills are guardrails for an -intelligent agent, not a step-by-step controller for a non-intelligent one — -calibrate prescription to the failure mode"** (hard rules for deterministic -safety, trust for judgment). - -**The inverse error matters too:** don't hard-code genuine judgment into brittle -rules. Seating 8 people by personality is *correctly* latent; only the -800-person optimization belongs in code. - ---- - -## 4. skill-as-method-call — parameterize, don't proliferate - -A skill file is a **parameterized procedure invoked like a method call**: the -user supplies the *what* (a target, a question, a depth tier); the skill -supplies the fixed *how*. - -- gstack's `/qa` ships **Quick / Standard / Exhaustive** tiers — one file, one - procedure, a named depth argument. -- gstack's `/investigate` is a fixed four-phase procedure re-aimed at any bug. -- Codex's `/goal` ships a literal six-slot signature. - -**The diagnostic test:** if different arguments run *different steps*, those are -**N skills wearing one filename** (the "god-skill" anti-pattern) — split them. -If the *process* stays fixed and only the *input/depth* varies, it's one method -— **don't fork it, parameterize it.** - ---- - -## 5. skill-pack-bundle — "a skill pack has tests" (the defining property) - -The frontier's definition of a skill as a *unit*: not just a `SKILL.md`, but a -**tested bundle**. The 10-step "skillify checklist": - -1. `SKILL.md` — the contract (name, triggers, rules) -2. deterministic code (`scripts/*`) — no LLM for what code can do -3. **unit tests** for the code -4. **integration tests** (live endpoints) -5. **LLM evals** (quality + correctness, LLM-as-judge) -6. **resolver trigger** (a routing-table entry) -7. **resolver eval** (verify the trigger actually routes) -8. **check-resolvable + DRY audit** (is the skill reachable? does it duplicate - another's lane?) -9. **E2E smoke test** -10. **filing rules** (knowledge-writing files by primary subject) - -> "A feature that doesn't pass all ten is not a skill. It's just code that -> happens to work today." - -Evidence: gstack ships `skill-validation.test.ts`, `skill-llm-eval.test.ts` -(~$0.15/run), `skill-e2e-*.test.ts` (~$3.85/run), three `*-resolver*.test.ts`, -run under **diff-based selection** + a **gate/periodic tier split** (cheap -deterministic tests block every PR; expensive LLM evals run on a weekly cron). -CE ships ~1,094 cases incl. behavioral *contract* tests. - -**The rarest, most-stealable part: the resolver eval** — a test that the right -skill *fires* (and wrong ones *don't*), not just that its output is good. Almost -nobody tests skill *triggering*. - ---- - -## 6. resolver-routing-table — tested routing at scale - -A **resolver** is a routing table for context: *when intent X appears, load -skill Y first.* Two philosophies: - -- **Explicit** (gbrain): a `RESOLVER.md` `trigger → skill` dispatcher read once - per request, plus filing rules, plus **15 `routing-eval.jsonl` fixtures** with - *both* polarities (must-fire positives AND adversarial must-not-fire - negatives), plus `check-resolvable` (a build gate that fails if any skill is - "dark"/unreachable). -- **Implicit** (gstack, Anthropic demos): lean on the harness's built-in - `description`-field auto-matching — *"the description **is** the resolver."* - -**The threshold that matters:** the observed breaking point is **~40 skills**. -Below it, the harness's implicit description-matching resolves cleanly. Past it, -three failures appear: skills drift in their filing; skills go **"dark"** (built -but unreachable — "a surgeon the hospital can't find"); and the table rots -within ~90 days. **A pack at/over ~40 skills with no resolver eval and no -reachability audit is exactly where implicit routing starts to break.** - ---- - -## 7. complexity-ratchet — forward-only quality - -Every session adds tests/docs/evals/learnings that **reload into context**, so -the quality floor only ever rises. Its load-bearing step (CE's `/ce-compound`): -write the solved problem into `docs/solutions/` with a **5-dimension overlap -dedup** (update the existing doc, don't create a near-dup), then a -**discoverability check** (does the project's `AGENTS.md` lead a *fresh* agent to -the store?). Branch protection is the "can't regress below the floor" mechanism. -Budget rule: **50/50** — half your effort on the system that produces work. - -> **The counter-weight — the "Foxconn factory."** The *same* author who preaches -> "test everything" warns against over-building systems that *police* a capable -> model: ~276K lines of tests, a 1,778-line fact-checker fanning every claim to -> five sources. The line: skill-pack tests **pin a thin unit so it can change -> safely** (contracts); a Foxconn factory is **mountains of code written to -> distrust the model** (a cage). Same word ("tests"), opposite spirit. When you -> score a pack's deterministic substrate, ask of each script: *is this exact -> work (math, parsing, validation) or model-policing code?* Only the first earns -> its keep. - ---- - -## 8. Other confirmed patterns worth naming - -- **single-source-multi-surface-distribution** — author once, ship to many - harnesses (CE converts to ~11; gbrain ships CLI + MCP + skillpack from one - definition; per-harness manifests + vendored copies with a drift check). -- **persona-lens-review-panel** — review via parallel narrow persona reviewers - (read-only, structured returns) merged by an orchestrator (CE: 43 review - subagents; PM OS: a 9-reviewer PRD panel). -- **diarization** — read everything about a subject, write **one page of - distilled judgment** (the "says vs. actually doing" gap). "No SQL query - produces this." The shape a good brief/dossier skill should target. -- **session-start-directive** — session-start hooks must emit *imperative, - position-pinned* directives, not passive context the model silently ignores. -- **version-as-update-gate** — `version` is the update-delivery trigger; release - automation owns it, hand-bumps forbidden. -- **completion-gating** — block "it's ready" claims until evidence exists. The - strongest form *externally verifies* (a script returns `ok`, required files - exist, no fabricated numbers) rather than letting the model *self-attest*. -- **immutable-source / anti-cheat** — a PreToolUse hook that blocks writes into - the immutable evidence/source tree, so the agent can't launder its inputs. - ---- - -## 9. How fat is "fat" — the anatomy, with real line numbers - -"Fat" is easy to mis-hear as "long." It isn't. A **fat skill encodes enough -judgment and process that the model reliably produces a finished, trustworthy -result without the user micromanaging it.** In the frontier packs that depth -*happens to be large*. Verified gstack `wc -l` of shipped `SKILL.md` bodies: - -``` -Tiny utility / toggle skills (the exception): - unfreeze 48 careful 67 guard 90 freeze 91 gstack-upgrade 284 -Setup / single-tool skills: - setup-browser-cookies 603 benchmark-models 631 benchmark 756 make-pdf 758 -Substantive capability / workflow skills (the norm — ~840 to 2,301): - investigate 1,045 qa 1,655 retro 1,783 autoplan 1,823 review 1,823 - design-review 1,965 spec 2,301 ... (median ≈ 1,030) -``` - -**What fills 1,000+ lines (the `/investigate` dissection):** ~60 lines rich -frontmatter (explicit trigger phrases + proactive-invoke conditions + -allowed-tools + context queries); ~120 lines bash preamble; ~100 lines first-run -onboarding; **~80 lines a strict interaction contract** (the AskUserQuestion -"decision brief" format: every question carries an ELI10 explanation, "stakes if -we pick wrong," a recommendation, ≥2 pros/≥1 con per option, a "Net" tradeoff -line, effort in both human-time and AI-time); then **the actual multi-phase -procedure** with an "Iron Law," worked sub-steps, evidence requirements, and exit -criteria; then completion + write-back discipline. Roughly **~750 of 1,045 lines -are encoded judgment** — *that* is fat. It encodes a senior engineer's entire -debugging discipline, not "debug the thing." - -### The four roles and their fatness norms - -| Skill role | Frontier norm | What fills it | -|---|---|---| -| **Orchestrator / workflow** | 1,300–3,000 effective lines | pipeline control, workspace contract, phase gates, landing recap | -| **Deep capability / leaf** | 800–1,700 lines | full multi-phase procedure + interaction contract + verification | -| **Reusable single-task leaf** | 100–400 lines + `references/` | one job, references for shared context, still a real procedure | -| **Demonstration skill** | 30–150 lines | shows the *format*; intentionally minimal; not production | - -So *"how fat should every skill be?"* is **not "uniformly enormous"** — it is -**tiered**: fat orchestrators over a library of focused (but still real, -references-backed) leaf skills. A pack whose substantive skills sit in the -bottom two rows is **thin** by the frontier bar; one whose workflow front-doors -sit in the top two rows (whether the depth is in the body *or* pushed into -scripts/references) is **fat**. - -> Depth can live in the `SKILL.md` body **or** in co-located `scripts/` + -> `references/`. Pushing deterministic depth into scripts is a *legitimate* (often -> better) choice — but it means the *judgment* layer (the prose procedure) can -> still be lean. Score both: total footprint **and** body judgment-depth. - ---- - -## 10. The size-budget discipline (fat ≠ unbounded) - -gstack ships a `skill-size-budget` test. It is **not** a fixed "max N lines" -cap. It's a free, gate-tier regression test that diffs the current tree against -a captured baseline and enforces four budgets: - -1. **Per-skill growth ceiling — ≤ 1.5× baseline.** A body may grow only ~1.5× - between baselines (started at 1.05, loosened to 1.50: "real bloat is 2-3×; - this catches that without tripping on normal feature scope"). An anti-bloat - ratchet keyed to *growth*, not an absolute count. -2. **Total-corpus ceiling — ≤ 1.5× baseline** (stops death-by-a-thousand-cuts). -3. **Per-skill shrink floor — ≥ 80% of baseline** (catches accidental - body-stripping; skills carved into `sections/` are exempted and guarded by a - skeleton+sections-union minimum instead). -4. **The only *absolute* cap — the always-loaded catalog ≤ ~7,000 tokens.** The - "catalog" is the set of skill **descriptions** that load into **every** - context window. Bodies load on demand (so they're only ratcheted); the - catalog is paid on every request (so it gets a hard ceiling). "Attention is - the scarce resource" turned into a test. - -**The rule for benchmarking:** fat *bodies* are fine because they're -lazy-loaded; a fat *catalog* is not. A pack whose summed descriptions exceed -~7K tokens has a real, measurable problem regardless of how good each skill is. -Push depth into on-demand `sections/`/`references/`; keep the always-loaded -description surface tight. diff --git a/plugins/recoup-records/.claude-plugin/plugin.json b/plugins/recoup-records/.claude-plugin/plugin.json deleted file mode 100644 index 3776caf..0000000 --- a/plugins/recoup-records/.claude-plugin/plugin.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "recoup-records", - "displayName": "Recoup Records", - "version": "0.1.1", - "description": "A record label in a box: setup, research, deals, roster, content, song analysis, and releases management", - "author": { - "name": "Recoup", - "email": "support@recoupable.com", - "url": "https://recoupable.com" - }, - "homepage": "https://recoupable.com", - "repository": "https://github.com/recoupable/skills", - "license": "AGPL-3.0-only", - "keywords": [ - "music", - "recoup", - "record-label", - "artist-management", - "music-research", - "catalog-deals", - "content-creation", - "release-management", - "agent-skills" - ], - "interface": { - "displayName": "Recoup Records", - "shortDescription": "A record label in a box", - "longDescription": "The whole Recoup platform as a single plugin: artist setup and API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release workflows — every skill, agent, and hook in one bundle.", - "developerName": "Recoup", - "category": "Music", - "capabilities": [ - "Read", - "Write" - ], - "websiteURL": "https://recoupable.com" - } -} diff --git a/plugins/recoup-records/.codex-plugin/plugin.json b/plugins/recoup-records/.codex-plugin/plugin.json deleted file mode 100644 index a27d2d7..0000000 --- a/plugins/recoup-records/.codex-plugin/plugin.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "recoup-records", - "version": "0.1.1", - "description": "A record label in a box — one install that bundles every Recoup plugin: setup, research, deals, content, song analysis, and releases. Install this instead of picking individual plugins.", - "author": { - "name": "Recoup", - "email": "agent@recoupable.com", - "url": "https://recoupable.com" - }, - "homepage": "https://recoupable.com", - "repository": "https://github.com/recoupable/skills", - "license": "AGPL-3.0-only", - "keywords": [ - "music", - "recoup", - "record-label", - "artist-management", - "music-research", - "catalog-deals", - "content-creation", - "release-management", - "agent-skills" - ], - "skills": "./skills/", - "interface": { - "displayName": "Recoup Records", - "shortDescription": "A record label in a box", - "longDescription": "The whole Recoup platform as a single plugin: artist setup and API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release workflows — every skill, agent, and hook in one bundle.", - "developerName": "Recoup", - "category": "Music", - "capabilities": [ - "Read", - "Write" - ], - "websiteURL": "https://recoupable.com", - "composerIcon": "./assets/recoup-icon.png", - "logo": "./assets/recoup-icon.png" - } -} diff --git a/plugins/recoup-records/.cursor-plugin/plugin.json b/plugins/recoup-records/.cursor-plugin/plugin.json deleted file mode 100644 index f2876da..0000000 --- a/plugins/recoup-records/.cursor-plugin/plugin.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "recoup-records", - "version": "0.1.1", - "description": "A record label in a box — one install that bundles every Recoup plugin: setup, research, deals, content, song analysis, and releases. Install this instead of picking individual plugins.", - "author": { - "name": "Recoup", - "email": "agent@recoupable.com", - "url": "https://recoupable.com" - }, - "homepage": "https://recoupable.com", - "repository": "https://github.com/recoupable/skills", - "license": "AGPL-3.0-only", - "keywords": [ - "music", - "recoup", - "record-label", - "artist-management", - "music-research", - "catalog-deals", - "content-creation", - "release-management", - "agent-skills" - ], - "logo": "assets/recoup-icon.png", - "skills": "./skills/", - "agents": "./agents/", - "interface": { - "displayName": "Recoup Records", - "shortDescription": "A record label in a box", - "longDescription": "The whole Recoup platform as a single plugin: artist setup and API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release workflows — every skill, agent, and hook in one bundle.", - "developerName": "Recoup", - "category": "Music", - "capabilities": [ - "Read", - "Write" - ], - "websiteURL": "https://recoupable.com" - } -} diff --git a/plugins/recoup-records/LICENSE b/plugins/recoup-records/LICENSE deleted file mode 100644 index 2101d55..0000000 --- a/plugins/recoup-records/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ -This project is licensed under the AGPL-3.0 for non-commercial use. - -Commercial Use - -For commercial use or deployments requiring a setup, please contact us for a -commercial license at support@recoupable.com. By using this software, you agree -to the terms of the license. - ----------------------------------------------------------------------- -The full text of the GNU Affero General Public License, version 3.0, -which governs non-commercial use, follows below. ----------------------------------------------------------------------- - - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/plugins/recoup-records/README.md b/plugins/recoup-records/README.md deleted file mode 100644 index 2e603d0..0000000 --- a/plugins/recoup-records/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Recoup — Record Label in a Box - -> **Skills are grouped into domains** — **capability** domains (`roster`, -> `research`, `song`, `content`, `release`, `catalog`) plus a **platform** domain -> (operating the system) — and every name is uniform -> `recoup-[domain]-[verb]-[noun]`. - -> **First-class, hand-maintained plugin.** This is the flagship "everything in -> one install" bundle, maintained directly — edit skills here directly. -> -> **Routing is governed by `RESOLVER.md`** (the skill dispatcher) and enforced by -> two CI gates: `scripts/check_resolvable.py` (every skill is reachable; no dark -> skills) and `scripts/run_resolver_eval.py` (routing fixtures in -> `resolver-eval.jsonl` at the plugin root, with coverage enforced — every skill -> needs a positive fixture). Add a `RESOLVER.md` row **and** a fixture whenever -> you add a skill. - -The whole Recoup platform as a single plugin: artist setup and API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release workflows — every skill, agent, and hook in one bundle. - -One install gives you the full platform — setup & API access, research, catalog -deals, content, song analysis, and releases — as a single self-contained plugin. - -## Install - -```bash -/plugin marketplace add recoupable/skills -/plugin install recoup-records@recoup -``` - -## Maintain - -Edit skills in `skills/` directly. After any change to the skill set, run the -routing gates (also run in CI): - -```bash -python3 scripts/check_resolvable.py # every skill reachable from RESOLVER.md -python3 scripts/run_resolver_eval.py # routing fixtures valid + full coverage -``` diff --git a/plugins/recoup-records/assets/recoup-icon.png b/plugins/recoup-records/assets/recoup-icon.png deleted file mode 100644 index 89d1b75c154a1d7edb93a9e7601afd72ee0765cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129041 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�r;tfbg#JX&RnK*H?70Asb}8T)WC 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 - Estimate the value and performance of a music catalog, album, or individual - recording from public/streaming data alone — no seller files, no royalty - statements required. Use this whenever the user wants to value a catalog or - asset, project annual Net Label Share (NLS) or Net Publisher Share, build a - catalog/portfolio baseline, measure how an asset is performing over time, run - a "streams → revenue → value" model, or produce an executive baseline report — - even if they don't say the words "Catalog Value Estimator". This works from - PUBLIC data only — for a full data-room valuation with the seller's royalty - statements, use recoup-catalogs instead. Triggers include - "what is this catalog worth", "value the K.I.D.S. masters", "estimate NLS", - "how is this album performing", "build a portfolio baseline", "streams to - revenue", "annualize streams", "catalog value from public data", or any - request to size/track a master or publishing asset using Songstats/streaming - data via the Recoup Research API. Prefer this skill over hand-rolled curl when - the goal is a defensible, reproducible value or performance number. ---- - -# Catalog Value Estimator - -Turn public streaming data into a defensible, reproducible estimate of a music -asset's **performance** and **value** — assembled with zero seller cooperation, -so it can be produced before a deal, used to verify a counterparty's claims, or -frozen as a baseline to measure impact over time. - -The whole method rests on one identity: - -> **Catalog value = sustainable annual Net Label Share (NLS) × a market multiple** - -NLS is what the owner *keeps* after distribution and artist/producer royalties — -not gross streaming revenue. The job of this skill is to measure real streams, -convert them to annual NLS through a labeled, auditable assumption stack, apply -a multiple, and surface the risks (concentration, rights flags) that move the -multiple — all while being honest about what is *measured* vs *assumed*. - -## Scope: a factual baseline, nothing more - -This skill produces a **baseline** — a frozen, defensible snapshot of what a -catalog *is and currently earns*, built so the owner can audit every number. -Everything it outputs is either **measured** (streams pulled live) or a -**labeled, auditable assumption** (per-stream rates, the deduction stack, the -multiple). That is the whole deliverable. - -**It deliberately does not:** - -- project uplift, growth, or "ΔV" from any intervention; -- estimate recoverable or "found" income, or quantify leakage in dollars; -- recommend actions, name opportunities, or describe what Recoup *would do* to - improve the catalog. - -Those are intervention-phase claims. Until they can be measured against a frozen -baseline (and, ideally, calibrated against a real royalty statement), they are -not verifiable — and an unverifiable claim in a baseline costs the trust the -baseline exists to build. Risk factors are reported as **neutral observations** -of what they are, never as a to-do list. Keep forward-looking work out of this -artifact entirely; it belongs in a separate, later deliverable. - -## When to use this - -Reach for this skill for any of: valuing a catalog/album/track, projecting -annual NLS, building a portfolio baseline, tracking an asset's performance over -time, or producing a streams→revenue→value report. It works on one recording or -thousands. It does **not** need royalty statements — when those exist, use them -to calibrate (see "Verification loop"). - -## Prerequisites - -- **Recoup API access.** All streaming data comes from the Recoup Research API - (Songstats-backed). Set `RECOUP_API_KEY` (sent as `x-api-key`) **or** - `RECOUP_ACCESS_TOKEN` (sent as `Authorization: Bearer`). In a Recoup sandbox - `RECOUP_ACCESS_TOKEN` is already present. To get a key, see - `references/recoup-api.md`. -- `python3` with `matplotlib` and `reportlab` (only for the PDF report): - `pip install --break-system-packages matplotlib reportlab`. -- `curl` available on PATH (the scripts shell out to it). - -## The loop (five steps) - -1. **Assemble the asset + identifiers.** Resolve the recordings you want to - value into Spotify track IDs or ISRCs, and confirm ownership from public - metadata (the `℗`/P-line, `labels`, and `distributors` returned by the API). - - Have ISRCs or Spotify track IDs already → skip to step 2. - - Have a Spotify album → `scripts/fetch_album_tracks.py --album ` - prints the track IDs. - - Have only an artist/label name → resolve the catalog first (see - `references/recoup-api.md`: `lookup`, `tracks`), - then feed the IDs in. -2. **Measure streams.** For every track, pull all-time per-platform counts - (`/research/track/stats`) and the trailing-12-month TTM from the - **`measurements`** resource. `scripts/estimate.py` does both. Spotify counts - are served from Recoup's measurement store (Apify-first; every entry carries - `data_source` + `captured_at` provenance) — they are platform-displayed play - counts, quota-free and refreshed within ~24h. The skill runs one flow in both - modes: **current counts → seed historical backfill → (wait for the instant - drain) → derive TTM.** - - **TTM is one read.** `GET /research/tracks/{id}/measurements?granularity=daily` - returns the stitched series; the skill derives `measured_365d` (full-year - span) vs `runrate_d` (short span) from it — replacing the old - `historic-stats` + `playcount-deltas` calls and all client-side delta math. - - **Portfolio scale (hundreds–thousands of tracks):** read current counts via - `GET /research/albums/{id}/measurements?latest=true`; uncaptured albums are - captured with a `current` measurement-job (`POST /research/measurement-jobs - {source:"current"}`, ~$0.003/album). See `references/recoup-api.md`. - - **Seed deep historical backfill, then capture it in the same run.** A real - `measured_365d` TTM needs a full year of daily history that only the - Songstats worker supplies, and **reads no longer auto-enqueue it** — so the - skill explicitly creates a *historical ingest job* - (`POST /research/measurement-jobs {scope, source:"historical"}`, ranked by - all-time streams, deduped server-side). The drain now **fires immediately** - (not a daily wait), so `--wait-backfill ` pauses after the seed and the - same run comes back with `measured_365d` for tracks the drain reached. - Disable seeding with `--no-backfill-seed`. **A card on file is required** - (Songstats is metered) — a cardless account gets a checkout link instead of a - seeded job, which the seed surfaces without failing the run. Quota is the - ceiling (~900 hits / 30 days, one per track) — see `references/methodology.md` - for the head-first prioritization, and `references/recoup-api.md` for the - `measurement-jobs` + `measurements` resource model (chat#1796) that the - legacy per-track endpoints consolidate into. -3. **Model gross → NLS → value.** Apply public per-stream rates, the deduction - stack, and the multiple band from `references/methodology.md`. Keep - *measured* platforms separate from *approximated* ones and carry a band, not - a point. -4. **Observe risk (neutrally).** Compute concentration (top-track and top-3 - share of streams) and note the factors that move the multiple — - single-track dependency, plateauing run-rate, sample-driven hits, - registration gaps. Report each as a *neutral observation of what it is* - (e.g. "top track is 70% of TTM streams"), not as an opportunity, a - remediation step, or a dollar of upside. These observations are as - important as the dollar figure. -5. **Report.** Emit the structured results, and — if the user wants something - shareable — a branded executive PDF via `scripts/build_report.py`. - -Run steps 2–4 with one command: - -```bash -python3 scripts/estimate.py \ - --ids # or --isrcs / --ids-file - --asset-name "K.I.D.S." --owner "Rostrum Records" \ - --out ./out -# -> out/estimate.json (full results) + out/summary.md (human-readable) -``` - -Then, optionally: - -```bash -python3 scripts/build_report.py --estimate ./out/estimate.json --out ./out -# -> out/-baseline-report.pdf (charts + valuation + methodology) -``` - -`estimate.py` and `build_report.py` are the workhorses — don't re-derive their -logic inline; call them so every run is identical and auditable. - -## Reading the output - -`estimate.json` contains, per asset: all-time and trailing-12-month streams per -platform, gross/NLS/value with low–central–high bands, the per-track table, the -concentration metrics, and the exact assumption set used (so any number can be -traced back). `summary.md` is the same thing in prose + a table. - -The `provenance` block reports measurement honesty, including -`deep_history_share` (fraction of tracks on a true `measured_365d` TTM) and -`backfill_seed` (what the run enqueued). On a freshly seeded catalog -`deep_history_share` starts low and the value leans on `runrate_*` proxies; -it climbs toward 100% as the backfill worker drains over the following weeks. -State that coverage when sharing a portfolio baseline — it is the difference -between a measured number and a run-rate estimate. - -Lead with what's **measured** (streams are real, pulled live) and present the -dollar figures as a **directional model** with assumptions visible. This matters -especially when sharing with a rights owner who knows their real numbers — -overclaiming a value you derived from public rates costs credibility. - -## Honest limits (state these in any output) - -- **Measured vs approximated platforms.** Songstats exposes per-track streams - for Spotify, YouTube, and SoundCloud; Apple/Amazon/Deezer/Tidal return - playlist data but not stream counts, so their revenue is a labeled gross-up. - The measured-platform figure is a floor; true revenue is higher. -- **Master-side only by default.** This values recordings (NLS). Publishing - (NPS — mechanical + performance) is separate and additive; only model it if - the owner controls the compositions. -- **History depth.** Spotify history is a stitched series from Recoup's - measurement store: snapshot captures (2026-06 onward) plus backfilled - Songstats points (day-level, typically from ~2021), each point labeled with - `data_source`. A track only has the deep series once the Songstats backfill - worker has filled it. **Enqueueing is not automatic for portfolio runs:** a - per-track `historic-stats` read enqueues lazily, but the snapshot/portfolio - path does not — that is why portfolio mode seeds the backfill explicitly - (Step 2). Until a track is drained it returns its snapshot-only series, so - early portfolio runs lean on `runrate_*` TTMs; re-run after the worker drains - to pick up `measured_365d`. The all-time cumulative always reflects the full - life regardless. -- **Every rate, deduction, and multiple is an assumption** (see methodology). - The multiple is the single biggest swing — concentration and a flat run-rate - argue for the low end. - -## Calibration & the verification loop - -When a public comp exists (a reported sale/ask), sanity-check the estimate -against it — but match scope (a single album vs a multi-album, multi-artist -portfolio). When a real royalty statement becomes available, solve for the -counterparty's true blended per-stream rate and deduction stack, replace the -defaults, and the band collapses. Each real statement makes the next -permissionless estimate more accurate — fold corrections back into the config. - -## What to read next - -- `references/methodology.md` — the full assumption set: per-stream rates, the - gross→NLS deduction stack, multiple bands, the gross-up, the TTM annualization - mechanic, calibration, and caveats. **Read this before quoting any number.** -- `references/recoup-api.md` — auth and the exact endpoints used (`track/stats`, - `track/historic-stats`, `tracks`, `lookup`, `spotify/album`), with examples. diff --git a/skills/catalog-value-estimator/references/methodology.md b/skills/catalog-value-estimator/references/methodology.md deleted file mode 100644 index 5c8208a..0000000 --- a/skills/catalog-value-estimator/references/methodology.md +++ /dev/null @@ -1,156 +0,0 @@ -# Methodology & Assumptions - -This file is the auditable assumption set behind every number the estimator -produces. The defaults live in `scripts/estimate.py` (the `DEFAULTS` block) and -can be overridden with `--config config.json`. Read this before quoting any -figure, and cite the relevant lines when you present results. - -## 1. Streams → gross receipts (master side) - -Gross master receipts ≈ Σ (streams on platform × public per-stream rate). 2025 -public rates (USD per stream/view), used as defaults: - -| Platform | Rate | Measured by API? | -|---|---|---| -| Spotify | $0.0035 (range 0.003–0.005) | ✅ `streams_total` | -| YouTube | $0.00069 (video views) | ✅ `video_views_total` (+ `short_views_total`) | -| SoundCloud | $0.0030 (range 0.0025–0.004) | ✅ `streams_total` | -| Apple Music | ~$0.01 | ❌ not exposed → gross-up | -| Amazon | ~$0.004 | ❌ not exposed → gross-up | -| Tidal | ~$0.0128 | ❌ not exposed → gross-up | -| Deezer | ~$0.004 | ❌ not exposed → gross-up | -| TikTok / Instagram | n/a (UGC / lump-sum license) | excluded from per-stream gross | - -**Measured gross** = Spotify + YouTube + SoundCloud (the platforms the API -returns stream counts for). Treat this as a **floor**. - -**Other-DSP gross-up.** Apple/Amazon/Deezer/Tidal stream counts are not exposed, -so approximate their revenue as a labeled percentage *of Spotify gross*. Apple -alone often rivals Spotify revenue (it pays ~3× the per-stream rate on a smaller -stream base). Default band: **+25% / +40% / +60%** of Spotify gross -(low/central/high). This is the largest source of measurement uncertainty — -always show it as a band. - -## 2. Gross → Net Label Share (NLS) - -NLS is what the label keeps. `NLS = gross × (1 − distribution%) × (1 − royalty%)`. - -- **Distribution fee** — the distributor's cut. Default **15%**. (Check the - `distributors` field on the track; e.g. WMG/Warner-distributed catalogs carry - a real Warner fee.) -- **Artist + producer royalties** — the performer's and producers' share of net - receipts. Default **25%** blended. For catalogs old enough to be recouped, - royalties are paid through, so this is a real reduction to the owner's keep. -- Resulting **NLS ≈ 55%–70% of gross** (central ~64%). Carry the band. - -If valuing **publishing** instead, compute **NPS (Net Publisher Share)** on the -composition side with its own splits — do not merge NPS and NLS into one "net". - -## 3. Annualization (the run-rate that gets the multiple) - -Value is built on **sustainable annual** NLS, not lifetime totals. Get the -annual run-rate from the **`measurements`** series -(`GET /research/tracks/{id}/measurements?granularity=daily` → `[{date, value, …}]`, -`value` cumulative as of `date`), so - -> **trailing-12-month streams = value(end_date) − value(end_date − 365d)** - -Use the cumulative values at the two window endpoints (robust to flat/duplicate -snapshot days); don't sum daily deltas. Spotify TTM is measured precisely; -approximate YouTube/SoundCloud TTM at their all-time share of Spotify gross to -avoid extra calls (small contributors). Day-level history generally begins -~2021, so trajectories may be ~5 years even for older catalogs. - -**TTM derivation labels.** Every track's TTM carries a `ttm_source`: - -- `measured_365d` — true 365-day diff from the stitched historic series. The - gold standard; use whenever backfilled history spans the window. -- `runrate_d` — annualized from the available span of the same - `measurements` series (computed client-side; no separate deltas call). A - **proxy**, accepted only when N ≥ `min_delta_days` (default 28): short windows - embed release spikes, - viral noise, display-count update lag, and uncorrected seasonality (Q4 - typically runs +20–30% vs January). Expect ±misstatement vs measured TTM - that shrinks as windows lengthen; do not mix derivations silently — the - estimate reports the mix and TTM coverage in `provenance`. -- `insufficient_window` / `none` — no accepted TTM; the track contributes - all-time presence but $0 to the TTM-based value (state the coverage %). - -When a material share of value rides on `runrate_*`, say so next to the -headline. The historical backfill drain now fires on seed, so `--wait-backfill` -lets a single run upgrade reached tracks to `measured_365d` instead of re-running -later. - -**Seeding the backfill (how `runrate_*` becomes `measured_365d`).** A -`measured_365d` TTM requires a full year of daily history, which only the -Songstats backfill worker supplies. That worker drains a queue, and **the -snapshot/portfolio read path never fills it** — so `estimate.py` in portfolio -mode seeds the catalog explicitly by creating a historical ingest job -(`POST /research/measurement-jobs {source:"historical"}`). Two rules: - -- **Prioritize head-first.** The queue is ranked by all-time streams, and so is - the seed. The value-bearing head dominates — for a multi-thousand-track - catalog the top several hundred tracks carry essentially all the NLS — so seed - (and judge "enough coverage") by value, not by raw track count. -- **Respect the quota ceiling.** One Songstats hit backfills one track's entire - history, permanently (it is never re-fetched). The plan allows ~1,000 hits per - rolling 30 days, ~100 reserved, so ≈900 tracks/30d. Backfilling the - value-bearing head (hundreds of tracks) is ~1 month; a full several-thousand - track catalog is several months and consumes nearly the whole research quota - for the duration. Seed the head, report `deep_history_share`, and let coverage - climb across cron runs rather than claiming full measured coverage on day one. - -## 4. NLS → value (the multiple) - -`value = annual NLS × multiple`. Master catalogs have traded broadly **8×–16×** -NLS in recent years (publishing higher). Default band **10× / 13× / 16×** -(low/central/high). The multiple is the biggest swing in the whole model: - -- **Down** the band: single-track concentration, flat/declining run-rate, - unsupported rights, sample-dependent income. -- **Up** the band: diversified, durable/evergreen, *growing* run-rate, clean and - documented rights. - -Choose the band deliberately and say *why* — a defensible value names the -multiple's drivers. - -## 5. Risk metrics that move the multiple - -- **Concentration.** Top-track and top-3 share of trailing-12-month streams. A - catalog where one track is the majority of streams is underwritten at a - discount and sits lower in the multiple band. Report the share as a neutral - observation; do not turn it into a growth recommendation in the baseline. -- **Trajectory.** YoY change in annual streams: growing vs plateaued vs - declining. Plateau/decline ⇒ mid/low multiple. -- **Rights flags.** Sample-driven hits (a share of income may flow to the - sampled rights holders; clearance affects transferability), missing - registrations (use SoundExchange ISRC Search / MLC to detect leakage), - reversions, territory limits. Flag, don't assume. - -## 6. Calibration - -If a public comp exists (reported sale or ask), compare — but **match scope**: a -single album is a fraction of a multi-album, multi-artist portfolio. A central -estimate landing at a sensible fraction of the comp is a passing sanity check; -a wild divergence means revisit assumptions (usually the multiple or the -gross-up). - -## 7. The verification loop (turning assumptions into actuals) - -Every default above is an industry estimate. When one real royalty statement -from the owner becomes available: - -1. Solve for their **true blended per-stream rate** (their reported receipts ÷ - their streams for the period). -2. Solve for their **real deduction stack** (reported NLS ÷ gross). -3. Replace the defaults in `--config` and re-run; the band collapses toward a - point. -4. Report the accuracy of the prior permissionless estimate, and fold the - correction forward so the next catalog is estimated better with no statement. - -## 8. Scope caveats to print in every output - -- Master-side only unless publishing is explicitly modeled. -- Measured = Spotify/YouTube/SoundCloud; other DSPs approximated. -- TikTok/Instagram excluded from per-stream gross (UGC/lump-sum). -- Figures are estimates derived from public rates, **not reported royalties**. diff --git a/skills/catalog-value-estimator/references/recoup-api.md b/skills/catalog-value-estimator/references/recoup-api.md deleted file mode 100644 index 8d46193..0000000 --- a/skills/catalog-value-estimator/references/recoup-api.md +++ /dev/null @@ -1,148 +0,0 @@ -# Recoup Research API — endpoints used by this skill - -All streaming data is pulled from the Recoup Research API. Spotify play counts -come from Recoup's own measurement store (Apify-first, provenance-labeled); -other sources are Songstats-backed. Base URL: `https://api.recoupable.com/api`. - -## Authentication - -Send one of: - -- `x-api-key: $RECOUP_API_KEY` — a key from `https://chat.recoupable.com/keys`, - or in one unauthenticated call: - ```bash - export RECOUP_API_KEY=$(curl -s -X POST "https://api.recoupable.com/api/agents/signup" \ - -H "Content-Type: application/json" \ - -d '{"email":"agent+'$(date +%s)'@recoupable.com"}' | jq -r .api_key) - ``` -- `Authorization: Bearer $RECOUP_ACCESS_TOKEN` — already present inside a Recoup - sandbox. - -`scripts/estimate.py` reads `RECOUP_API_KEY` first, then `RECOUP_ACCESS_TOKEN`. - -## Endpoints - -### `GET /research/track/stats` — per-track current stats -Resolve by `isrc`, `songstats_track_id`, `spotify_track_id`, or -`apple_music_track_id`. `source` (required) is a comma list or `all`. -Returns `stats: [{ source, data, data_source, captured_at? }]` + `track_info`. -Spotify entries are served from the measurement store when an `isrc` is given -(`data_source: "apify_spotify_playcount"`, ~24h freshness, no Songstats quota); -other sources are Songstats (`data_source: "songstats"`). The per-source `data` -includes the absolute play count: -- spotify: `streams_total` -- youtube: `video_views_total`, `short_views_total` -- soundcloud: `streams_total` -- tiktok/instagram: `views_total` (UGC — not used for per-stream gross) -`track_info` carries `title`, `labels`, `distributors`, `links` (with `isrc`). - -```bash -curl -sS -H "x-api-key: $RECOUP_API_KEY" \ - "https://api.recoupable.com/api/research/track/stats?isrc=USQY51771120&source=all" -``` -Note: `streams_total` may be returned as a numeric string (e.g. `"296422273.0"`) -— parse as float. - -### `GET /research/tracks/{id}/measurements` — per-track measured series + run-rate -The consolidated read for a track's history (replaces `track/historic-stats` **and** -`track/playcount-deltas`). `{id}` is provider-neutral (ISRC or Spotify track id, -resolved server-side). -- default / `?granularity=daily` → `series[]` of `{date, value, data_source}` - (`value` is cumulative as of `date`). Trailing-12-month = last − first over a - 365-day window. The stitched store series (Apify snapshots + Songstats backfill, - labeled per point) carries both the measured year and the recent run-rate. -- `?aggregate=run_rate&window=365d` → `aggregate: {kind, window_days, delta, - run_rate_annualized}` — a server-computed run-rate projection. - -`estimate.py` reads the series and derives `measured_365d` (full-year span) vs -`runrate_d` (short span) itself — one read, no legacy endpoints, no client-side -delta calls. Reads no longer auto-enqueue backfill; seed it explicitly (see -`measurement-jobs`). - -```bash -curl -sS -H "x-api-key: $RECOUP_API_KEY" \ - "https://api.recoupable.com/api/research/tracks/USA2P2015959/measurements?granularity=daily" -``` - -### `GET /spotify/album` — enumerate an album's tracks -Returns the album with `tracks.items[].id` (Spotify track IDs) plus -`copyrights` (the `℗` line) and `label`. Used by -`scripts/fetch_album_tracks.py`. - -### `GET /research/tracks` — all tracks for an artist (with ISRCs + Songstats ids) -`?artist=` or `?id=`. Returns `tracks[]` with `isrcs` and -`songstats_track_id`. Use to assemble a catalog when starting from an artist. - -### `GET /research/lookup` — resolve a Spotify artist URL/ID → provider record -`?spotifyId=`. Returns `artist_info.links` across 18 platforms (incl. -MusicBrainz id) — useful for cross-referencing/ownership. - -### `GET /research/metrics` — artist-level metrics (context, not per-track) -`?artist=&source=spotify`. Returns artist totals (`streams_total`, -`monthly_listeners_current`, playlists, charts) across 16 sources. Use for a -roster/portfolio context snapshot, not for per-asset valuation (it's the whole -catalog, not one recording). - -### `GET /research/albums/{id}/measurements` — latest per-track counts for an album -`{id}` = Spotify album id; `?latest=true`. Returns `measurements[]` of -`{isrc, spotify_track_id, name, value, captured_at, data_source}` from the most -recent capture (replaces `GET /research/playcounts`). 404 when the album has never -been captured — create a `current` measurement-job (below). Only mapped tracks -are returned. - -```bash -curl -sS -H "x-api-key: $RECOUP_API_KEY" \ - "https://api.recoupable.com/api/research/albums/70Zkfb99ladZ3q0JVg97co/measurements?latest=true" -``` - -### `POST /research/measurement-jobs` — ingest current or historical counts (async) - -One async ingest resource for both capture modes (chat#1796). Body: -`{ scope: {album_ids[] | isrcs[] | catalog_id}, source: "current" | "historical" }`. - -- `source:"current"` — Apify capture of present counts (**absorbs `POST /research/snapshots`**). - Returns **202** `{ status, source:"current", id, state:"queued", album_count, estimated_cost_usd }` - (`id` is the snapshot job). -- `source:"historical"` — enqueue each resolved recording for Songstats deep - backfill (`rank_score` = all-time streams) so the daily worker fills its full - daily history. **Idempotent:** songs already carrying `songstats` history are - skipped; no track is fetched from Songstats twice. Returns **202** - `{ status, source:"historical", id:null, enqueued, skipped }`. - **Requires a card on file** — Songstats is metered, so a cardless account gets - **402** with a Stripe `checkoutUrl` (free tier) instead of a job; - `estimate.py`'s seed surfaces that link rather than aborting the run. - -A `historical` job is the **only** way to backfill at portfolio scale — the -snapshot/portfolio read path enqueues nothing. There is no per-resource status -endpoint; read run status from the generic `GET /api/tasks/runs?runId=`. - -```bash -curl -sS -X POST -H "x-api-key: $RECOUP_API_KEY" -H "Content-Type: application/json" \ - -d '{"scope":{"album_ids":["70Zkfb99ladZ3q0JVg97co"]},"source":"historical"}' \ - "https://api.recoupable.com/api/research/measurement-jobs" -``` - -## Resource model (chat#1796, live in prod) - -Reads are one `measurements` collection (track series + `aggregate=run_rate` + -album `latest`); writes are one `measurement-jobs` resource (`source:current` for -Apify capture, `source:historical` for Songstats backfill). The old RPC paths — -`track/historic-stats`, `track/playcount-deltas`, `playcounts`, `snapshots` — are -deprecated and removed from the docs; `estimate.py` uses only the consolidated -resources above plus `track/stats`. - -## Rate-limit / robustness notes - -- Spotify reads never spend Songstats quota (store-served). The Songstats - rolling quota (~1,000 hits/30d) applies only to non-Spotify sources; for - portfolio-scale work, snapshot first instead of per-track polling. -- Historic calls return a full daily series; for many tracks, request only the - sources you need and run sequentially with a per-call timeout (`curl - --max-time`). `estimate.py` does this and batches to avoid long single calls. -- Always handle `streams_total` as a possibly-stringified float. -- Every read endpoint charges 5 credits and may return **402** with a - `checkoutUrl` when the account is out of credits; snapshots are uncharged - but capped per org per month. -- Calibration: snapshot-derived counts agreed with Songstats `streams_total` - to ±0.1–1.7% on the 679-track Rostrum calibration set (2026-06-09) — label - them platform-displayed play counts, not royalty-bearing streams. diff --git a/skills/catalog-value-estimator/scripts/build_report.py b/skills/catalog-value-estimator/scripts/build_report.py deleted file mode 100755 index fa67df7..0000000 --- a/skills/catalog-value-estimator/scripts/build_report.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env python3 -"""Build a branded executive baseline PDF from estimate.json (output of estimate.py). -Usage: python3 build_report.py --estimate out/estimate.json --out out -Requires: matplotlib, reportlab. -> out/-baseline-report.pdf""" -import argparse, json, os, re, datetime as dt -import matplotlib; matplotlib.use("Agg") -import matplotlib.pyplot as plt -from matplotlib.ticker import FuncFormatter - -ap = argparse.ArgumentParser(); ap.add_argument("--estimate", required=True); ap.add_argument("--out", default=".") -A = ap.parse_args(); R = json.load(open(A.estimate)); OUT = A.out; os.makedirs(OUT, exist_ok=True) -slug = re.sub(r"[^A-Za-z0-9]+", "-", R["asset"]).strip("-") or "asset" - -INK="#0F172A"; ACCENT="#7C3AED"; ACCENT2="#06B6D4"; MUTED="#64748B"; LIGHT="#F1F5F9"; GREEN="#16A34A"; GRID="#E2E8F0" -plt.rcParams.update({"font.size":11,"font.family":"DejaVu Sans","axes.edgecolor":MUTED,"axes.labelcolor":INK, - "text.color":INK,"xtick.color":MUTED,"ytick.color":MUTED,"axes.linewidth":0.8,"figure.dpi":200}) -mil = lambda x,_: f"{x:,.0f}M" -def Mb(x): return f"{x/1e9:.2f}B" if x>=1e9 else f"{x/1e6:.0f}M" -def Dm(x): return f"${x/1e6:.2f}M" if x<1e9 else f"${x/1e9:.2f}B" - -traj = R.get("trajectory", {}) or {} -yks = sorted(int(y) for y in traj) -charts = [] - -if len(yks) >= 2: - cum = [traj[str(y)]["cumulative"]/1e6 for y in yks] - fig,ax=plt.subplots(figsize=(6.6,2.9)); ax.fill_between([str(y) for y in yks],cum,color=ACCENT,alpha=0.12) - ax.plot([str(y) for y in yks],cum,color=ACCENT,lw=2.6,marker="o",ms=6,mfc="white",mec=ACCENT,mew=2) - for x,v in zip(yks,cum): ax.annotate(Mb(v*1e6),(str(x),v),textcoords="offset points",xytext=(0,9),ha="center",fontsize=9,fontweight="bold") - ax.yaxis.set_major_formatter(FuncFormatter(mil)); ax.set_ylim(0,max(cum)*1.18) - ax.set_title("Cumulative Spotify streams",loc="left",fontsize=12,fontweight="bold",pad=10) - ax.grid(axis="y",color=GRID); [ax.spines[s].set_visible(False) for s in("top","right")] - fig.tight_layout(); p=f"{OUT}/_c_cum.png"; fig.savefig(p,bbox_inches="tight"); plt.close(); charts.append(("Cumulative streams",p)) - # annual + YoY - anns=[(y,traj[str(y)].get("annual")) for y in yks if traj[str(y)].get("annual") is not None] - if anns: - labs=[f"{y-1}–{str(y)[2:]}" for y,_ in anns]; vals=[v/1e6 for _,v in anns] - fig,ax=plt.subplots(figsize=(6.6,2.9)); bars=ax.bar(labs,vals,color=ACCENT,width=0.6) - for i,(b,v) in enumerate(zip(bars,vals)): - ax.annotate(f"{v:.0f}M",(b.get_x()+b.get_width()/2,v),textcoords="offset points",xytext=(0,5),ha="center",fontsize=9,fontweight="bold") - if i>0: - g=100*vals[i]/vals[i-1]-100 - ax.annotate(f"{g:+.0f}%",(b.get_x()+b.get_width()/2,v/2),ha="center",fontsize=10,fontweight="bold",color="white") - ax.yaxis.set_major_formatter(FuncFormatter(mil)); ax.set_ylim(0,max(vals)*1.2) - ax.set_title("Annual streams & YoY change",loc="left",fontsize=12,fontweight="bold",pad=10) - ax.grid(axis="y",color=GRID); [ax.spines[s].set_visible(False) for s in("top","right")] - fig.tight_layout(); p=f"{OUT}/_c_ann.png"; fig.savefig(p,bbox_inches="tight"); plt.close(); charts.append(("Annual streams",p)) - -# concentration -tr=R["tracks"][:8]; rest=R["tracks"][8:]; total=R["streams"]["spotify_ttm"] or 1 -names=[t["title"][:26] for t in tr]; vals=[t["spotify_ttm"]/1e6 for t in tr] -if rest: names.append(f"Other ({len(rest)} tracks)"); vals.append(sum(t['spotify_ttm'] for t in rest)/1e6) -fig,ax=plt.subplots(figsize=(6.6,3.4)); cols=[ACCENT]+[ACCENT2]*(len(vals)-2)+[GRID] if len(vals)>1 else [ACCENT] -ax.barh(range(len(names)),vals,color=cols[:len(names)]); ax.set_yticks(range(len(names))); ax.set_yticklabels(names,fontsize=9); ax.invert_yaxis() -for i,v in enumerate(vals): ax.annotate(f"{v:.0f}M ({100*v*1e6/total:.0f}%)",(v,i),textcoords="offset points",xytext=(6,0),va="center",fontsize=8.3) -ax.xaxis.set_major_formatter(FuncFormatter(mil)); ax.set_xlim(0,max(vals)*1.25) -ax.set_title("Trailing-12-month streams by track — concentration",loc="left",fontsize=12,fontweight="bold",pad=10) -ax.grid(axis="x",color=GRID); [ax.spines[s].set_visible(False) for s in("top","right","left")] -fig.tight_layout(); p=f"{OUT}/_c_conc.png"; fig.savefig(p,bbox_inches="tight"); plt.close(); charts.append(("Concentration",p)) - -# value range -v=R["value"]; lbl=["Conservative","Central","Upside"]; vv=[v["low"]/1e6,v["central"]/1e6,v["high"]/1e6] -fig,ax=plt.subplots(figsize=(6.6,2.9)); bars=ax.bar(lbl,vv,color=[MUTED,ACCENT,ACCENT2],width=0.55) -for b,x in zip(bars,vv): ax.annotate(f"${x:.0f}M",(b.get_x()+b.get_width()/2,x),textcoords="offset points",xytext=(0,6),ha="center",fontsize=12,fontweight="bold") -ax.yaxis.set_major_formatter(FuncFormatter(lambda x,_:f"${x:.0f}M")); ax.set_ylim(0,max(vv)*1.18) -ax.set_title("Estimated asset value (directional model)",loc="left",fontsize=12,fontweight="bold",pad=10) -ax.grid(axis="y",color=GRID); [ax.spines[s].set_visible(False) for s in("top","right")] -fig.tight_layout(); p=f"{OUT}/_c_val.png"; fig.savefig(p,bbox_inches="tight"); plt.close(); charts.append(("Value",p)) - -# ---- PDF ---- -from reportlab.lib.pagesizes import letter; from reportlab.lib.units import inch; from reportlab.lib import colors -from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle -from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, Table, TableStyle, HRFlowable -INKc=colors.HexColor(INK); ACc=colors.HexColor(ACCENT); MUc=colors.HexColor(MUTED); LTc=colors.HexColor(LIGHT); GRc=colors.HexColor(GRID) -ss=getSampleStyleSheet() -def S(n,**k): b=k.pop("parent",ss["Normal"]); return ParagraphStyle(n,parent=b,**k) -h1=S("h1",fontName="Helvetica-Bold",fontSize=17,textColor=INKc,leading=20); h2=S("h2",fontName="Helvetica-Bold",fontSize=12.5,textColor=ACc,spaceBefore=13,spaceAfter=6) -body=S("b",fontSize=10,textColor=INKc,leading=15,spaceAfter=6); small=S("s",fontSize=8.2,textColor=MUc,leading=11) -bullet=S("bl",fontSize=10,textColor=INKc,leading=15,leftIndent=10,spaceAfter=3) -def kpi(num,lab,bg=LTc,nc=INKc,lc=MUc): - t=Table([[Paragraph(num,S('n',fontName="Helvetica-Bold",fontSize=16,textColor=nc))],[Paragraph(lab,S('l',fontSize=7.4,textColor=lc))]],colWidths=[1.62*inch]) - t.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),bg),("LEFTPADDING",(0,0),(-1,-1),9),("RIGHTPADDING",(0,0),(-1,-1),9), - ("TOPPADDING",(0,0),(0,0),9),("BOTTOMPADDING",(0,0),(0,0),1),("TOPPADDING",(0,1),(0,1),0),("BOTTOMPADDING",(0,1),(-1,-1),9),("ROUNDEDCORNERS",[5,5,5,5])])); return t - -cfg=R["assumptions"]; conc=R["concentration"] -story=[] -hdr=Table([[Paragraph('recoup',S('lg',fontName="Helvetica-Bold",fontSize=15)), - Paragraph('PORTFOLIO BASELINE REPORT',S('hr',fontSize=8.5,textColor=MUc,alignment=2))]],colWidths=[3.4*inch,3.6*inch]) -hdr.setStyle(TableStyle([("VALIGN",(0,0),(-1,-1),"MIDDLE"),("LEFTPADDING",(0,0),(-1,-1),0),("RIGHTPADDING",(0,0),(-1,-1),0)])) -story+=[hdr,Spacer(1,4),HRFlowable(width="100%",thickness=2,color=ACc,spaceAfter=12), - Paragraph(f"{R['asset']}" + (f" — {R['owner']}" if R['owner'] else ""),h1), - Paragraph(f"Asset performance & valuation baseline · As of {R['as_of']}",S('sub',fontSize=10.5,textColor=MUc,spaceAfter=10)), - Paragraph("Measured baseline built from independent streaming and public data — no owner-supplied files. The reference " - "point against which the impact of Recoup’s work on this asset is measured over time.",body)] -k=Table([[kpi(Mb(R['streams']['spotify_all_time']),"All-time Spotify streams"), - kpi(Mb(R['streams']['spotify_ttm']),"Streams, trailing 12 mo."), - kpi("~"+Dm(R['annual_nls']['central']),"Annual Net Label Share*"), - kpi("~"+Dm(R['value']['central']),"Est. asset value*",bg=ACc,nc=colors.white,lc=colors.HexColor("#E9D5FF"))]],colWidths=[1.72*inch]*4) -k.setStyle(TableStyle([("LEFTPADDING",(0,0),(-1,-1),0),("RIGHTPADDING",(0,0),(-1,-1),4),("TOPPADDING",(0,0),(-1,-1),0),("BOTTOMPADDING",(0,0),(-1,-1),0)])) -story+=[Spacer(1,8),k,Spacer(1,2),Paragraph("*Directional model — streams measured live; revenue/value from public per-stream " - "rates and labeled assumptions (see Methodology). Estimates, not reported royalties.",small)] -if R.get("distributors"): - story+=[Spacer(1,4),Paragraph(f"Owner: {R['owner'] or 'n/a'} · Distributor(s): {', '.join(R['distributors'])} · Recordings: {R['n_tracks']}",small)] - -for title,pth in charts: - h = 3.4 if title=="Concentration" else 2.9 - story+=[Paragraph(title,h2) if title in ("Concentration",) else Spacer(1,6), Image(pth,width=6.6*inch,height=h*inch)] - if title=="Concentration": - story+=[Paragraph(f"The catalog is concentrated: top track “{conc['top_track']}” is {conc['top_track_share']*100:.0f}% of " - f"trailing-12-month streams; the top three are {conc['top3_share']*100:.0f}%. Single-track dependency is typically " - "underwritten at a discount and is one of the drivers of the multiple.",small)] - -g=R['annual_gross']['central']; nls=R['annual_nls']['central']; df=cfg['distribution_fee']; roy=cfg['artist_producer_royalty']; m=cfg['multiple']['central'] -flow=[["Annual gross receipts (all platforms, est.)",Dm(g)], - [f"less distribution fee (~{df*100:.0f}%)","−"+Dm(g*df)], - [f"less artist + producer royalties (~{roy*100:.0f}%)","−"+Dm(g*(1-df)*roy)], - ["= Annual Net Label Share (NLS)",Dm(nls)], - [f"× market multiple ({cfg['multiple']['low']}–{cfg['multiple']['high']}×, central {m}×)",f"× {m}"], - ["= Estimated asset value (central)","~"+Dm(R['value']['central'])]] -ft=Table([[Paragraph(x,S('fa',fontSize=9.5)),Paragraph(y,S('fb',fontSize=9.5,fontName="Helvetica-Bold",alignment=2))] for x,y in flow],colWidths=[4.2*inch,1.6*inch]) -ft.setStyle(TableStyle([("LEFTPADDING",(0,0),(-1,-1),8),("RIGHTPADDING",(0,0),(-1,-1),8),("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5), - ("LINEBELOW",(0,0),(-1,-1),0.4,GRc),("BACKGROUND",(0,3),(-1,3),LTc),("BACKGROUND",(0,5),(-1,5),colors.HexColor("#EDE9FE")), - ("FONTNAME",(0,3),(-1,3),"Helvetica-Bold"),("LINEABOVE",(0,3),(-1,3),0.8,MUc),("LINEABOVE",(0,5),(-1,5),0.8,ACc)])) -story+=[Paragraph("Revenue & valuation (directional model)",h2), - Paragraph("Catalog value = annual Net Label Share (NLS) × a market multiple. NLS is what the label keeps after distribution " - "and artist/producer royalties:",body),ft] -story+=[Paragraph("Methodology & assumptions",h2), - Paragraph("Data. Streams measured live via the Recoup Research API (Songstats), per-track current & historic stats by " - f"ISRC; ownership from public metadata. Rates. Public 2025 per-stream rates (Spotify ${cfg['rates']['spotify']}, " - f"YouTube ${cfg['rates']['youtube']}, SoundCloud ${cfg['rates']['soundcloud']}); Apple/Amazon/Deezer/Tidal not exposed and " - "approximated as a labeled gross-up; TikTok/Instagram excluded. Deductions. Distribution ~" - f"{df*100:.0f}%, artist+producer ~{roy*100:.0f}%; NLS {cfg['nls_band']['low']*100:.0f}–{cfg['nls_band']['high']*100:.0f}% of gross. " - f"Multiple. {cfg['multiple']['low']}–{cfg['multiple']['high']}× NLS. Scope. Master-side only; publishing separate. " - "Verification. Every default is replaceable by a real royalty statement, which collapses the range.",small)] - -def footer(c,d): - c.saveState(); c.setStrokeColor(GRc); c.setLineWidth(0.6); c.line(0.8*inch,0.62*inch,7.7*inch,0.62*inch) - c.setFont("Helvetica",7.5); c.setFillColor(MUc) - c.drawString(0.8*inch,0.46*inch,f"Recoup · Confidential · Baseline as of {R['as_of']}") - c.drawRightString(7.7*inch,0.46*inch,f"Page {c.getPageNumber()}"); c.restoreState() -pdf=f"{OUT}/{slug}-baseline-report.pdf" -SimpleDocTemplate(pdf,pagesize=letter,topMargin=0.7*inch,bottomMargin=0.8*inch,leftMargin=0.8*inch,rightMargin=0.8*inch, - title=f"{R['asset']} Baseline Report",author="Recoup").build(story,onFirstPage=footer,onLaterPages=footer) -print("PDF:",pdf) diff --git a/skills/catalog-value-estimator/scripts/estimate.py b/skills/catalog-value-estimator/scripts/estimate.py deleted file mode 100755 index 7af8a9f..0000000 --- a/skills/catalog-value-estimator/scripts/estimate.py +++ /dev/null @@ -1,421 +0,0 @@ -#!/usr/bin/env python3 -""" -Catalog Value Estimator — core. -Given recordings (Spotify track IDs / ISRCs) or whole albums (Spotify album IDs), -pull streams from the Recoup Research API, annualize, and model -gross -> NLS -> value with labeled assumptions. Outputs estimate.json + summary.md. - -Flow (both modes): current counts -> seed deep historical backfill -> optionally -wait for the instant drain -> derive TTM. Counts come from track/stats (track -mode) or albums/{id}/measurements (portfolio mode); the trailing-12-month TTM -comes from the consolidated tracks/{id}/measurements series — one read that -replaces historic-stats + playcount-deltas (chat#1796). With `--wait-backfill`, -tracks the drain reaches in this run come back as `measured_365d` rather than a -short-window run-rate. - -Every track in the output carries `data_source`, `captured_at`, and -`ttm_source` (measured_365d | runrate_d | insufficient_window | none). - -Auth: env RECOUP_API_KEY (x-api-key) or RECOUP_ACCESS_TOKEN (Bearer). -See references/methodology.md for the assumption set; defaults below. -""" -import argparse, json, os, subprocess, sys, time, datetime as dt -from collections import Counter -from concurrent.futures import ThreadPoolExecutor - -BASE = os.environ.get("RECOUP_API_BASE", "https://api.recoupable.com/api") - -DEFAULTS = { - "rates": {"spotify": 0.0035, "youtube": 0.00069, "soundcloud": 0.0030}, - "other_dsp_grossup": {"low": 0.25, "central": 0.40, "high": 0.60}, # of Spotify gross - "distribution_fee": 0.15, - "artist_producer_royalty": 0.25, - "nls_band": {"low": 0.55, "central": 0.64, "high": 0.70}, # NLS as % of gross - "multiple": {"low": 10, "central": 13, "high": 16}, - "window_days": 365, - "trajectory_years": 5, - "min_delta_days": 28, # shortest capture window accepted as a TTM proxy -} - -def auth_header(): - k = os.environ.get("RECOUP_API_KEY") - if k: return ["-H", f"x-api-key: {k}"] - t = os.environ.get("RECOUP_ACCESS_TOKEN") - if t: return ["-H", f"Authorization: Bearer {t}"] - sys.exit("ERROR: set RECOUP_API_KEY or RECOUP_ACCESS_TOKEN (see references/recoup-api.md).") - -def api(path, params=None, body=None, timeout=20, soft=False): - url = f"{BASE}/{path}" - if params: url += "?" + "&".join(f"{k}={v}" for k, v in params.items()) - cmd = ["curl", "-sS", "--max-time", str(timeout)] + auth_header() - if body is not None: - cmd += ["-X", "POST", "-H", "Content-Type: application/json", "-d", json.dumps(body)] - out = subprocess.run(cmd + [url], capture_output=True, text=True).stdout - try: j = json.loads(out) - except Exception: return {} - # A 402 carries a Stripe checkoutUrl. For required reads we stop the run; for - # best-effort calls (soft=True, e.g. the backfill seed) we hand the body back - # so the caller can surface the link without aborting the whole estimate. - if not soft and isinstance(j, dict) and j.get("checkoutUrl"): - sys.exit(f"ERROR: out of credits (402). Top up at: {j['checkoutUrl']}") - return j - -def num(x): - try: return float(str(x)) - except Exception: return 0.0 - -def id_param(ident): - s = ident.strip() - if len(s) == 12 and s[:2].isalpha(): return ("isrc", s) - return ("spotify_track_id", s) - -# ---------------- TTM from the measurements series (chat#1796) ---------------- -def measurements_series(track_id): - """Daily measured Spotify series for a track from the consolidated - `measurements` resource — one read that replaces both `track/historic-stats` - and `track/playcount-deltas`. `{track_id}` is provider-neutral (ISRC or - Spotify track id, resolved server-side). Returns [{date, value, data_source}].""" - j = api(f"research/tracks/{track_id}/measurements", {"granularity": "daily"}, timeout=25) - return j.get("series") or [] - -def compute_ttm(series, asof, cfg): - """Derive (sp_ttm, ttm_source) from one measured series: a true - `measured_365d` diff when the series spans the trailing year, otherwise a - short-window run-rate from the series' own span (`runrate_d` when - N >= min_delta_days). Replaces the client-side snapshot-delta math — the same - series now carries both the Songstats backfill and the Apify snapshot points.""" - if not series: return 0.0, "none" - try: - dates = sorted(dt.date.fromisoformat(p["date"]) for p in series if p.get("date")) - except Exception: - dates = [] - if not dates: return 0.0, "none" - win_start = asof - dt.timedelta(days=cfg["window_days"]) - end_v = nearest(series, asof.isoformat()) - start_v = nearest(series, win_start.isoformat()) - span_ok = dates[0] <= win_start + dt.timedelta(days=45) and dates[-1] >= asof - dt.timedelta(days=45) - if end_v is not None and start_v is not None and span_ok: - return max(0.0, end_v - start_v), "measured_365d" - # fall back to a run-rate over the available (short) span - days = (dates[-1] - dates[0]).days - if days < cfg["min_delta_days"]: return 0.0, "insufficient_window" - v0, v1 = nearest(series, dates[0].isoformat()), nearest(series, dates[-1].isoformat()) - if v0 is None or v1 is None: return 0.0, "insufficient_window" - return max(0.0, (v1 - v0) / days * 365), f"runrate_{int(days)}d" - -# ---------------- track mode ---------------- -def track_current(ident): - key, val = id_param(ident) - j = api("research/track/stats", {key: val, "source": "all"}) - d = {"id": ident, "title": ident, "isrc": val if key == "isrc" else "", "sp": 0, "yt": 0, - "sc": 0, "tt": 0, "labels": [], "distributors": [], - "data_source": None, "captured_at": None} - ti = j.get("track_info", {}) or {} - d["title"] = ti.get("title", ident) - d["labels"] = [l.get("name") for l in ti.get("labels", []) if isinstance(l, dict)] - d["distributors"] = [x.get("name") for x in ti.get("distributors", []) if isinstance(x, dict)] - for ln in ti.get("links", []) or []: - if ln.get("isrc"): d["isrc"] = ln["isrc"]; break - for s in j.get("stats", []) or []: - src, data = s.get("source"), s.get("data", {}) or {} - if src == "spotify": - d["sp"] = num(data.get("streams_total")) - d["data_source"] = s.get("data_source") - d["captured_at"] = s.get("captured_at") - elif src == "youtube": d["yt"] = num(data.get("video_views_total")) + num(data.get("short_views_total")) - elif src == "soundcloud": d["sc"] = num(data.get("streams_total")) - elif src == "tiktok": d["tt"] = num(data.get("views_total")) - return d - -def nearest(series, target): - if not series: return None - tt = dt.date.fromisoformat(target) - best, bestdiff = None, 10**9 - for h in series: - try: dd = dt.date.fromisoformat(h["date"]) - except Exception: continue - diff = abs((dd - tt).days) - if diff < bestdiff: bestdiff, best = diff, num(h.get("value")) - return best - -def track_history(d, asof, years, cfg): - """Fills sp_ttm, ttm_source, anniversaries on dict d from the measurements - series (provider-neutral id; one read replaces historic-stats + deltas).""" - series = measurements_series(d.get("isrc") or d["id"]) - d["sp_ttm"], d["ttm_source"] = compute_ttm(series, asof, cfg) - anns = {} - for k in range(years + 1): - dd = asof - dt.timedelta(days=365 * k) - v = nearest(series, dd.isoformat()) - if v is not None: anns[dd.year] = v - d["anniversaries"] = anns - return d - -# ---------------- portfolio (snapshot-first) mode ---------------- -def album_measurements(album_id): - """Latest measured count per track on an album from the `measurements` - resource (replaces GET /research/playcounts). Items: {isrc, spotify_track_id, - name, value, captured_at, data_source}.""" - j = api(f"research/albums/{album_id}/measurements", {"latest": "true"}) - return j.get("measurements") or [] - -def portfolio_tracks(album_ids, snapshot=True, wait_mins=6): - """Current per-track counts for a catalog. Captures uncaptured albums via a - `current` measurement-job (replaces POST /research/snapshots). TTM is filled - separately by fill_ttm() — after the historical seed, so an instant drain - yields measured_365d in the same run.""" - rows, missing = [], [] - for aid in album_ids: - m = album_measurements(aid) - if m: rows += [(aid, p) for p in m] - else: missing.append(aid) - if missing and snapshot: - print(f" {len(missing)} albums uncaptured -> POST /research/measurement-jobs (current)", file=sys.stderr) - j = api("research/measurement-jobs", body={"scope": {"album_ids": missing}, "source": "current"}) - print(f" capture job {j.get('id','?')} queued, est ${j.get('estimated_cost_usd','?')}", file=sys.stderr) - deadline = time.time() + wait_mins * 60 - while missing and time.time() < deadline: - time.sleep(20) - still = [] - for aid in missing: - m = album_measurements(aid) - if m: rows += [(aid, p) for p in m] - else: still.append(aid) - missing = still - print(f" waiting on {len(missing)} albums...", file=sys.stderr) - if missing: - print(f" WARNING: {len(missing)} albums still uncaptured (re-run later): {missing[:5]}...", file=sys.stderr) - tracks, seen = [], set() - for aid, p in rows: - tid = p.get("spotify_track_id") or p.get("isrc") - if not tid or tid in seen: continue - seen.add(tid) - tracks.append({"id": tid, "title": p.get("name", tid), "isrc": p.get("isrc", ""), - "sp": num(p.get("value")), "yt": 0, "sc": 0, "tt": 0, - "labels": [], "distributors": [], "album_id": aid, - "data_source": p.get("data_source"), "captured_at": p.get("captured_at"), - "anniversaries": {}}) - return tracks, missing - -def fill_ttm(tracks, asof, cfg, skip_ttm=False): - """Fill sp_ttm + ttm_source from each track's measurements series. Call AFTER - the historical backfill seed so tracks the instant drain reached come back as - measured_365d, not a short-window run-rate.""" - if skip_ttm: - for t in tracks: t["sp_ttm"], t["ttm_source"] = 0.0, "skipped" - return tracks - workers = int(os.environ.get("ESTIMATE_WORKERS", "8")) - def fill(t): - series = measurements_series(t.get("isrc") or t["id"]) - t["sp_ttm"], t["ttm_source"] = compute_ttm(series, asof, cfg); return t - with ThreadPoolExecutor(max_workers=workers) as ex: list(ex.map(fill, tracks)) - return tracks - -# ---------------- historical backfill seeding ---------------- -def seed_backfill(scope): - """Create a *historical* measurement-job so the Songstats worker fills in each - track's deep daily history — the instant drain then upgrades TTMs from a - short-window run-rate to `measured_365d` (poll with `--wait-backfill`). `scope` - is `{"album_ids":[...]}`, `{"isrcs":[...]}`, or `{"catalog_id":...}`. The job - ranks by all-time streams and dedupes server-side (songs already carrying - `songstats` history are skipped — no track is fetched twice). Reads no longer - enqueue backfill, so this seed is explicit. Best-effort: any error is logged - and never fails the estimate. Resource model: chat#1796.""" - if not scope or not any(scope.values()): - return {"note": "no scope to seed"} - # soft=True: the historical source is gated on a card on file, so a cardless - # account gets a 402 + checkoutUrl. Handle it here instead of aborting the run. - j = api("research/measurement-jobs", soft=True, - body={"scope": scope, "source": "historical"}, timeout=30) - if isinstance(j, dict) and j.get("checkoutUrl"): - return {"note": "deep-history backfill needs a payment method on file (Songstats is " - "metered) — add one to enable it: " + j["checkoutUrl"]} - if not j or (isinstance(j, dict) and j.get("error")): - return {"note": "POST /research/measurement-jobs errored — deep history not seeded this run"} - return {"enqueued": j.get("enqueued"), "skipped_already_backfilled": j.get("skipped")} - - -# ---------------- main ---------------- -def main(): - ap = argparse.ArgumentParser() - g = ap.add_mutually_exclusive_group(required=True) - g.add_argument("--ids"); g.add_argument("--isrcs"); g.add_argument("--ids-file") - g.add_argument("--album-ids", help="comma-separated Spotify album ids (snapshot-first portfolio mode)") - g.add_argument("--album-ids-file") - ap.add_argument("--asset-name", default="Catalog") - ap.add_argument("--owner", default="") - ap.add_argument("--asof", default=dt.date.today().isoformat()) - ap.add_argument("--config"); ap.add_argument("--out", default="./out") - ap.add_argument("--no-trajectory", action="store_true") - ap.add_argument("--no-snapshot", action="store_true", help="portfolio mode: don't auto-trigger snapshots") - ap.add_argument("--skip-ttm", action="store_true", help="portfolio mode: all-time only (no delta calls)") - ap.add_argument("--no-backfill-seed", action="store_true", - help="don't seed deep Songstats historical backfill") - ap.add_argument("--wait-backfill", type=int, default=0, - help="seconds to wait after seeding so the instant drain can upgrade " - "TTMs to measured_365d in this same run (0 = don't wait)") - ap.add_argument("--wait-mins", type=int, default=6) - a = ap.parse_args() - - cfg = json.loads(json.dumps(DEFAULTS)) - if a.config: cfg.update(json.load(open(a.config))) - asof = dt.date.fromisoformat(a.asof) - os.makedirs(a.out, exist_ok=True) - - def maybe_wait(): - if a.wait_backfill and not a.no_backfill_seed and not a.skip_ttm: - print(f" waiting {a.wait_backfill}s for the instant backfill drain...", file=sys.stderr) - time.sleep(a.wait_backfill) - - portfolio_mode = bool(a.album_ids or a.album_ids_file) - uncaptured = [] - if portfolio_mode: - if a.album_ids: album_ids = [x.strip() for x in a.album_ids.split(",") if x.strip()] - else: album_ids = [x.strip() for x in open(a.album_ids_file) if x.strip()] - # current counts -> seed historical backfill -> (wait for instant drain) -> TTM - tracks, uncaptured = portfolio_tracks(album_ids, snapshot=not a.no_snapshot, wait_mins=a.wait_mins) - backfill_seed = ({"note": "skipped (--no-backfill-seed)"} if a.no_backfill_seed - else seed_backfill({"album_ids": album_ids})) - print(f" [backfill] seed -> {backfill_seed}", file=sys.stderr) - maybe_wait() - fill_ttm(tracks, asof, cfg, skip_ttm=a.skip_ttm) - for i, t in enumerate(tracks): - print(f" [{i+1}/{len(tracks)}] {t['title'][:34]:34} all-time {t['sp']:>13,.0f} " - f"TTM {t['sp_ttm']:>12,.0f} ({t['ttm_source']})", file=sys.stderr) - years = 0 - else: - if a.ids: idents = [x for x in a.ids.split(",") if x.strip()] - elif a.isrcs: idents = [x for x in a.isrcs.split(",") if x.strip()] - else: idents = [x.strip() for x in open(a.ids_file) if x.strip()] - years = 0 if a.no_trajectory else cfg["trajectory_years"] - workers = int(os.environ.get("ESTIMATE_WORKERS", "6")) - # 1. current per-track stats (resolves ISRCs) -> 2. seed backfill -> 3. wait -> 4. TTM/history - with ThreadPoolExecutor(max_workers=workers) as ex: - tracks = list(ex.map(track_current, idents)) - isrcs = [t["isrc"] for t in tracks if t.get("isrc")] - backfill_seed = ({"note": "skipped (--no-backfill-seed)"} if a.no_backfill_seed - else seed_backfill({"isrcs": isrcs}) if isrcs else {"note": "no ISRCs resolved"}) - print(f" [backfill] seed -> {backfill_seed}", file=sys.stderr) - maybe_wait() - def fetch(t): - return track_history(t, asof, max(years, 1), cfg) - with ThreadPoolExecutor(max_workers=workers) as ex: - list(ex.map(fetch, tracks)) - for i, cur in enumerate(tracks): - print(f" [{i+1}/{len(tracks)}] {cur['title'][:34]:34} all-time {cur['sp']:>13,.0f} " - f"TTM {cur['sp_ttm']:>12,.0f} ({cur['ttm_source']})", file=sys.stderr) - - R = cfg["rates"] - sp_all = sum(t["sp"] for t in tracks); yt_all = sum(t["yt"] for t in tracks) - sc_all = sum(t["sc"] for t in tracks); tt_all = sum(t["tt"] for t in tracks) - sp_ttm = sum(t["sp_ttm"] for t in tracks) - - def value_grid(g_sp): - g_ytsc = (yt_all * R["youtube"] + sc_all * R["soundcloud"]) - ratio = (g_ytsc / (sp_all * R["spotify"])) if sp_all else 0.0 - measured = g_sp + g_sp * ratio - out = {} - for sk, sv in cfg["other_dsp_grossup"].items(): - gross = measured + g_sp * sv - out[sk] = {"gross": gross, "nls": {nk: gross * nv for nk, nv in cfg["nls_band"].items()}} - return out - - annual = value_grid(sp_ttm * R["spotify"]) - central_gross = annual["central"]["gross"]; central_nls = annual["central"]["nls"]["central"] - central_value = central_nls * cfg["multiple"]["central"] - low_value = annual["low"]["nls"]["low"] * cfg["multiple"]["low"] - high_value = annual["high"]["nls"]["high"] * cfg["multiple"]["high"] - - ranked = sorted(tracks, key=lambda t: t["sp_ttm"], reverse=True) - top1 = ranked[0]["sp_ttm"] / sp_ttm if sp_ttm else 0 - top3 = sum(t["sp_ttm"] for t in ranked[:3]) / sp_ttm if sp_ttm else 0 - - traj = {} - if years: - yrset = set() - for t in tracks: yrset |= set(t["anniversaries"].keys()) - for y in sorted(yrset): - traj[y] = {"cumulative": sum(t["anniversaries"].get(y, 0) for t in tracks), - "tracks": sum(1 for t in tracks if y in t["anniversaries"])} - yk = sorted(traj) - for i in range(1, len(yk)): - traj[yk[i]]["annual"] = traj[yk[i]]["cumulative"] - traj[yk[i-1]]["cumulative"] - - caps = [t["captured_at"] for t in tracks if t.get("captured_at")] - provenance = { - "mode": "portfolio_snapshot_first" if portfolio_mode else "per_track", - "data_sources": dict(Counter(t.get("data_source") or "unknown" for t in tracks)), - "ttm_sources": dict(Counter(t.get("ttm_source") or "unknown" for t in tracks)), - "captured_at_min": min(caps) if caps else None, - "captured_at_max": max(caps) if caps else None, - "ttm_coverage_share": (sum(1 for t in tracks if t["sp_ttm"] > 0) / len(tracks)) if tracks else 0, - "deep_history_share": (sum(1 for t in tracks if t.get("ttm_source") == "measured_365d") / len(tracks)) if tracks else 0, - "backfill_seed": backfill_seed, - "uncaptured_albums": uncaptured, - "notes": "runrate_* TTM is a short-window annualization (>=%dd accepted) — noisier than measured_365d; " - "seasonality uncorrected. Portfolio mode measures Spotify only (yt/sc enter via gross-up)." - % cfg["min_delta_days"], - } - - result = { - "asset": a.asset_name, "owner": a.owner, "as_of": a.asof, "n_tracks": len(tracks), - "assumptions": cfg, "provenance": provenance, - "streams": {"spotify_all_time": sp_all, "youtube_all_time": yt_all, - "soundcloud_all_time": sc_all, "tiktok_all_time": tt_all, - "spotify_ttm": sp_ttm}, - "annual_gross": {k: v["gross"] for k, v in annual.items()}, - "annual_nls": {"low": annual["low"]["nls"]["low"], "central": central_nls, - "high": annual["high"]["nls"]["high"]}, - "value": {"low": low_value, "central": central_value, "high": high_value}, - "concentration": {"top_track": ranked[0]["title"] if ranked else "", - "top_track_share": top1, "top3_share": top3}, - "trajectory": traj, - "tracks": [{"title": t["title"], "isrc": t["isrc"], "spotify_all_time": t["sp"], - "spotify_ttm": t["sp_ttm"], "ttm_source": t.get("ttm_source"), - "data_source": t.get("data_source"), "captured_at": t.get("captured_at")} - for t in ranked], - "labels": sorted({l for t in tracks for l in t["labels"] if l}), - "distributors": sorted({d for t in tracks for d in t["distributors"] if d}), - } - json.dump(result, open(f"{a.out}/estimate.json", "w"), indent=2) - - M = lambda x: f"{x/1e9:.2f}B" if x >= 1e9 else f"{x/1e6:.1f}M" - D = lambda x: f"${x/1e6:.2f}M" - ttm_mix = ", ".join(f"{k}: {v}" for k, v in provenance["ttm_sources"].items()) - src_mix = ", ".join(f"{k}: {v}" for k, v in provenance["data_sources"].items()) - lines = [f"# {a.asset_name} — Catalog Value Estimate", "", - f"*Owner: {a.owner or 'n/a'} · As of {a.asof} · {len(tracks)} recordings · " - f"directional model (see methodology). Streams measured; $ figures derived from public rates.*", "", - "## Headline", "", - f"- **Estimated value:** {D(low_value)} – {D(high_value)} (central **{D(central_value)}**)", - f"- **Annual (trailing-12mo) NLS:** {D(annual['low']['nls']['low'])} – {D(annual['high']['nls']['high'])} (central **{D(central_nls)}**)", - f"- **All-time Spotify streams:** {M(sp_all)} · **trailing 12 mo:** {M(sp_ttm)}", - f"- **Concentration:** top track \"{ranked[0]['title'] if ranked else '—'}\" = {top1*100:.0f}% of TTM streams; top 3 = {top3*100:.0f}%", - "", "## Provenance", "", - f"- **Counts:** {src_mix} (captured {str(provenance['captured_at_min'])[:10]} → {str(provenance['captured_at_max'])[:10]})", - f"- **TTM derivation:** {ttm_mix} · TTM coverage {provenance['ttm_coverage_share']*100:.0f}% of tracks", - f"- **Deep history (measured_365d):** {provenance['deep_history_share']*100:.0f}% of tracks · " - f"backfill seed: {provenance['backfill_seed']}", - ""] - if result["distributors"]: - lines.append(f"- **Distributor(s):** {', '.join(result['distributors'])}") - if traj: - lines += ["", "## 5-year trajectory (Spotify)", "", - "| As of | Cumulative | Streams that year |", "|---|---|---|"] - for y in sorted(traj): - t = traj[y]; ann = t.get("annual") - lines.append(f"| {y} | {M(t['cumulative'])} | {M(ann) if ann is not None else '—'} |") - lines += ["", "## Value model (central)", "", - f"Annual gross ≈ {D(central_gross)} → NLS (×{cfg['nls_band']['central']:.2f}) ≈ {D(central_nls)} " - f"→ value (×{cfg['multiple']['central']}) ≈ {D(central_value)}.", "", - "## Caveats", "", - "- Counts are platform-displayed play counts (provenance-labeled per track); not royalty-bearing streams.", - "- `runrate_*` TTM is a short-window annualization — noisier than `measured_365d`; seasonality uncorrected.", - "- Measured = Spotify (+YouTube/SoundCloud in track mode); Apple/Amazon/Deezer/Tidal approximated (gross-up). Master-side only.", - "- Every rate/deduction/multiple is an assumption — calibrate with a real statement to collapse the range."] - open(f"{a.out}/summary.md", "w").write("\n".join(lines)) - print(f"\nWrote {a.out}/estimate.json and {a.out}/summary.md", file=sys.stderr) - print(f"VALUE central={D(central_value)} NLS central={D(central_nls)} TTM={M(sp_ttm)}", file=sys.stderr) - -if __name__ == "__main__": - main() diff --git a/skills/catalog-value-estimator/scripts/fetch_album_tracks.py b/skills/catalog-value-estimator/scripts/fetch_album_tracks.py deleted file mode 100755 index be76e65..0000000 --- a/skills/catalog-value-estimator/scripts/fetch_album_tracks.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -"""Resolve a Spotify album -> its track IDs (to feed estimate.py --ids). -Usage: python3 fetch_album_tracks.py --album -Prints a comma-separated list of track IDs; ownership (label / P-line) to stderr.""" -import argparse, json, os, re, subprocess, sys -BASE = os.environ.get("RECOUP_API_BASE", "https://api.recoupable.com/api") - -def auth(): - k = os.environ.get("RECOUP_API_KEY") - if k: return ["-H", f"x-api-key: {k}"] - t = os.environ.get("RECOUP_ACCESS_TOKEN") - if t: return ["-H", f"Authorization: Bearer {t}"] - sys.exit("set RECOUP_API_KEY or RECOUP_ACCESS_TOKEN") - -ap = argparse.ArgumentParser(); ap.add_argument("--album", required=True); a = ap.parse_args() -m = re.search(r"album/([A-Za-z0-9]+)", a.album); album_id = m.group(1) if m else a.album -url = f"{BASE}/spotify/album?id={album_id}&market=US" -j = json.loads(subprocess.run(["curl","-sS","--max-time","15"]+auth()+[url], capture_output=True, text=True).stdout or "{}") -ids = [t["id"] for t in j.get("tracks", {}).get("items", []) if t.get("id")] -label = j.get("label", ""); cr = "; ".join(c.get("text","") for c in j.get("copyrights", [])) -print(f"# album: {j.get('name','?')} label: {label} {cr}", file=sys.stderr) -print(f"# {len(ids)} tracks", file=sys.stderr) -print(",".join(ids)) diff --git a/skills/chart-metric/README.md b/skills/chart-metric/README.md deleted file mode 100644 index 7887824..0000000 --- a/skills/chart-metric/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Chartmetric - -A skill for AI agents to access music industry analytics via the Chartmetric API. - -## Install - -```bash -npx skills add recoupable/chartmetric -``` - -## What It Does - -Query streaming metrics, playlist placements, audience demographics, and competitive insights for any artist, track, or album. - -### Key Capabilities - -- **Artist Discovery** - Filter artists by Spotify listeners, TikTok followers, geography, genre -- **Platform Metrics** - Track performance across 14 platforms (Spotify, Instagram, TikTok, YouTube, etc.) -- **Playlist Intelligence** - Current and historical playlist placements with curator data -- **Audience Demographics** - Geographic and demographic breakdowns -- **Similar Artists** - Find related artists by audience, genre, mood, or musicality -- **Catalog Analysis** - Albums, tracks, career timeline - -## Quick Start - -```bash -# Search for an artist -python scripts/search_artist.py "Drake" - -# Get artist profile -python scripts/get_artist.py 3380 - -# Discover artists by metrics -python scripts/discover_artists.py --country US --spotify-listeners 100000 500000 -``` - -## Requirements - -- Chartmetric API subscription -- Python 3.8+ with `requests` package -- Set `CHARTMETRIC_REFRESH_TOKEN` environment variable - -## Documentation - -See [SKILL.md](SKILL.md) for complete usage documentation, all available scripts, and workflow examples. - -## Curl Fallback - -If Python is unavailable, see [references/curl-fallback.md](references/curl-fallback.md) for curl-only patterns. - -## License - -This project is licensed under the AGPL-3.0 for non-commercial use. - -### Commercial Use - -For commercial use or deployments requiring a setup, please contact us for a -commercial license at support@recoupable.com. By using this software, you agree -to the terms of the license. diff --git a/skills/chart-metric/SKILL.md b/skills/chart-metric/SKILL.md deleted file mode 100644 index 8db3f28..0000000 --- a/skills/chart-metric/SKILL.md +++ /dev/null @@ -1,265 +0,0 @@ ---- -name: chartmetric -description: Chartmetric music analytics API for streaming data, playlist placements, audience demographics, and competitive analysis. Use when the user needs artist/track/album analytics, playlist tracking, streaming metrics, audience insights, chart positions, similar artists, or any music industry data. Triggers on requests involving Spotify followers, monthly listeners, TikTok trends, Instagram audience, playlist pitching research, or competitive artist analysis. ---- - -# Chartmetric API - -Music industry analytics via Python scripts. Get streaming metrics, playlist placements, audience data, and competitive insights. - -## Setup - -### With Python (recommended) - -```bash -cd .recoup/skills/chartmetric -python3 -m venv .venv && source .venv/bin/activate -pip install requests -export CHARTMETRIC_REFRESH_TOKEN="your_token" -``` - -### Without Python (curl fallback) - -If Python is unavailable, see `references/curl-fallback.md` for curl-only patterns. Only requires `curl` (universal). - -## Quick Start - -```bash -# 1. Search for an artist -python scripts/search_artist.py "Drake" -# Returns: Chartmetric ID 3380 - -# 2. Get profile and stats -python scripts/get_artist.py 3380 -python scripts/get_artist_metrics.py 3380 --source spotify - -# 3. Get where fans listen -python scripts/get_artist_cities.py 3380 -``` - ---- - -## Critical Gotchas - -These are the API traps that cause 401/400 errors. Memorize them: - -| Gotcha | Wrong | Correct | -|--------|-------|---------| -| YouTube metrics | `--source youtube` | `--source youtube_channel` or `youtube_artist` | -| Artist playlists | `/artist/:id/playlists` | `/artist/:id/:platform/current/playlists` | -| Similar artists | `/artist/:id/similar` | `/artist/:id/relatedartists` or `similar-artists/by-configurations` | -| TikTok charts | With `country_code` | **No** `country_code` parameter | -| Amazon charts | With `country_code` | **No** `country_code` - use `genre` + `type` | -| Curator search | By name | By **numeric** Chartmetric ID only | - ---- - -## Scripts Reference - -### Artist Discovery - -```bash -# Find US artists with 100K-500K Spotify monthly listeners -python scripts/discover_artists.py --country US --spotify-listeners 100000 500000 - -# TikTok-famous but weak on Spotify -python scripts/discover_artists.py --tiktok-followers 1000000 10000000 --spotify-listeners 0 100000 - -# Emerging artists in a genre, sorted by weekly growth -python scripts/discover_artists.py --genre 86 --spotify-listeners 50000 200000 --sort weekly_diff.sp_monthly_listeners - -# Solo female artists in Brazil -python scripts/discover_artists.py --country BR --band false --pronoun she/her - -# Festival performers with low Spotify (undervalued) -python scripts/discover_artists.py --festival-id 123 --spotify-followers 0 50000 -``` - -**Available filters:** `--country`, `--genre`, `--band`, `--pronoun`, `--spotify-listeners`, `--spotify-followers`, `--tiktok-followers`, `--instagram-followers`, `--youtube-subscribers`, `--cpp`, `--festival-id` - -**Sort columns:** `latest.sp_monthly_listeners`, `weekly_diff.sp_monthly_listeners`, `monthly_diff.tt_followers`, etc. - -### Search & Lookup - -```bash -python scripts/search_artist.py "Taylor Swift" # Search artists by name -python scripts/get_artist.py 2762 # Get profile by CM ID -python scripts/get_artist_by_spotify.py 3TVXtAsR... # Lookup by Spotify ID/URL -python scripts/get_track.py 128613854 # Get track metadata -python scripts/get_track_by_spotify.py 0VjIjW4G... # Lookup track by Spotify -``` - -### Artist Data - -```bash -python scripts/get_artist_albums.py 3380 # All albums -python scripts/get_artist_tracks.py 3380 # All tracks -python scripts/get_artist_cities.py 3380 # Top cities by listeners -python scripts/get_artist_urls.py 3380 # Social/streaming URLs -python scripts/get_artist_insights.py 3380 # AI-generated insights -python scripts/get_artist_career.py 3380 # Career timeline -``` - -### Platform Metrics - -```bash -python scripts/get_artist_metrics.py 3380 --source spotify -python scripts/get_artist_metrics.py 3380 --source instagram -python scripts/get_artist_metrics.py 3380 --source youtube_channel -``` - -**Valid sources (14 total):** `spotify`, `instagram`, `tiktok`, `twitter`, `facebook`, `youtube_channel`, `youtube_artist`, `soundcloud`, `deezer`, `twitch`, `line`, `melon`, `wikipedia`, `bandsintown` - -### Audience Demographics - -```bash -python scripts/get_artist_audience.py 3380 # Instagram (default) -python scripts/get_artist_audience.py 3380 --platform tiktok # TikTok -python scripts/get_artist_audience.py 3380 --platform youtube # YouTube -python scripts/get_artist_instagram_posts.py 3380 # Top IG posts/reels -``` - -### Playlist Placements - -```bash -# Basic - current Spotify playlists -python scripts/get_artist_playlists.py 3380 - -# Other platforms -python scripts/get_artist_playlists.py 3380 --platform applemusic -python scripts/get_artist_playlists.py 3380 --platform deezer - -# Past placements -python scripts/get_artist_playlists.py 3380 --status past - -# With filters -python scripts/get_artist_playlists.py 3380 --editorial --newMusicFriday -python scripts/get_artist_playlists.py 3380 --indie --majorCurator - -# With date range and sorting -python scripts/get_artist_playlists.py 3380 --since 2025-01-01 --sort followers --limit 100 -``` - -**Platforms:** `spotify`, `applemusic`, `deezer`, `amazon`, `youtube` - -**Spotify filters:** `--editorial`, `--personalized`, `--chart`, `--thisIs`, `--newMusicFriday`, `--radio`, `--indie`, `--majorCurator`, `--popularIndie`, `--brand` - -### Similar/Related Artists - -```bash -# Basic related artists -python scripts/get_similar_artists.py 3380 --limit 10 - -# Advanced with configuration filters -python scripts/get_similar_artists.py 3380 --by-config --audience high --genre high -python scripts/get_similar_artists.py 3380 --by-config --mood medium --musicality high -``` - -**Config options:** `--audience`, `--mood`, `--genre`, `--musicality` (values: `high`, `medium`, `low`) - -### Playlists & Curators - -```bash -python scripts/get_playlist.py spotify 37i9dQZF1DXcBWIGoYBM5M # Playlist metadata -python scripts/get_curator.py 1 # Curator info (numeric ID) -``` - -### Discovery - -```bash -python scripts/list_genres.py # All Chartmetric genres -python scripts/list_festivals.py # Music festivals -``` - ---- - -## Workflow Chains - -### Research an Artist for Playlist Pitching - -```bash -# 1. Find the artist -python scripts/search_artist.py "Phoebe Bridgers" -# ID: 241089 - -# 2. Get their current playlist placements -python scripts/get_artist_playlists.py 241089 --editorial --limit 50 - -# 3. Find similar artists who might share playlists -python scripts/get_similar_artists.py 241089 --by-config --genre high --audience high - -# 4. Check where their fans are -python scripts/get_artist_cities.py 241089 -python scripts/get_artist_audience.py 241089 --platform instagram -``` - -### Competitive Analysis - -```bash -# 1. Get base artist -python scripts/get_artist.py 3380 - -# 2. Get similar artists with metrics -python scripts/get_similar_artists.py 3380 --by-config --audience high --limit 25 - -# 3. Compare streaming growth -python scripts/get_artist_metrics.py 3380 --source spotify -python scripts/get_artist_metrics.py --source spotify - -# 4. Compare playlist reach -python scripts/get_artist_playlists.py 3380 --sort followers -python scripts/get_artist_playlists.py --sort followers -``` - -### From Spotify URL to Full Profile - -```bash -# 1. Convert Spotify URL to Chartmetric ID -python scripts/get_artist_by_spotify.py "https://open.spotify.com/artist/3TVXtAsR1Inumwj472S9r4" -# Returns: cm_artist: 1320 - -# 2. Get everything -python scripts/get_artist.py 1320 -python scripts/get_artist_metrics.py 1320 --source spotify -python scripts/get_artist_cities.py 1320 -python scripts/get_artist_playlists.py 1320 -python scripts/get_artist_audience.py 1320 -``` - -### More Advanced Workflows - -See `references/advanced-workflows.md` for 10 strategic workflow chains including: -- Playlist pitching intelligence -- TikTok-to-Spotify pipeline analysis -- A&R discovery workflow -- Collaboration finder -- Viral song autopsy - ---- - -## References - -| File | When to Use | -|------|-------------| -| `references/parameter-guide.md` | Detailed endpoint parameters and gotchas | -| `references/endpoints.md` | All 120 endpoints with status | -| `references/curl-fallback.md` | **When Python unavailable** - curl patterns for all endpoints | -| `references/advanced-workflows.md` | **Strategic insights** - 10 multi-step workflow chains | - ---- - -## Rate Limits - -Chartmetric has rate limits. If you get 429 errors, wait 60 seconds before retrying. - ---- - -## Troubleshooting - -| Error | Cause | Fix | -|-------|-------|-----| -| 401 Unauthorized | Token expired or internal endpoint | Refresh token; check if endpoint is public | -| 402 Payment Required | Subscription expired | Check Chartmetric account | -| 404 Not Found | Invalid ID | Verify Chartmetric ID (not Spotify ID) | -| 400 Bad Request | Wrong parameters | Check parameter-guide.md for correct params | -| "Domain does not exist" | Invalid stat source | Use exact source names (e.g., `youtube_channel` not `youtube`) | diff --git a/skills/chart-metric/references/advanced-workflows.md b/skills/chart-metric/references/advanced-workflows.md deleted file mode 100644 index 1bbee8d..0000000 --- a/skills/chart-metric/references/advanced-workflows.md +++ /dev/null @@ -1,314 +0,0 @@ -# Advanced Workflow Chains - -Multi-step workflows that chain Chartmetric endpoints to unlock powerful insights. Each workflow answers a strategic question. - ---- - -## 1. Playlist Pitching Intelligence - -**Question:** "Which playlist curators should I pitch to?" - -```bash -# 1. Find similar artists who are slightly bigger (good benchmarks) -python scripts/get_similar_artists.py 241089 --by-config --audience high --genre high --limit 50 - -# 2. For each similar artist, get their playlist placements -python scripts/get_artist_playlists.py --editorial --indie --limit 100 - -# 3. Look for playlist overlap - curators who added multiple similar artists -# These curators are most likely to add your artist - -# 4. For promising playlists, get curator details -python scripts/get_playlist.py spotify -python scripts/get_curator.py - -# 5. Check if target artist was ever on these playlists -python scripts/get_artist_playlists.py 241089 --status past -``` - -**Output:** List of curators who already playlist similar artists but haven't added yours yet. - ---- - -## 2. TikTok-to-Spotify Pipeline Analysis - -**Question:** "Is TikTok virality translating to Spotify growth?" - -```bash -# 1. Get TikTok metrics over time -python scripts/get_artist_metrics.py 3380 --source tiktok - -# 2. Get Spotify metrics over time (same period) -python scripts/get_artist_metrics.py 3380 --source spotify - -# 3. Get TikTok audience demographics -python scripts/get_artist_audience.py 3380 --platform tiktok - -# 4. Get Spotify listener cities -python scripts/get_artist_cities.py 3380 - -# 5. Compare: Are TikTok audience countries matching Spotify growth cities? -# 6. Get top Instagram posts (often cross-posted from TikTok) -python scripts/get_artist_instagram_posts.py 3380 -``` - -**Output:** Correlation between TikTok spikes and Spotify follower/listener growth. Identify if there's a geographic mismatch (TikTok viral in Brazil but Spotify listeners in US = opportunity). - ---- - -## 3. Tour Routing Intelligence - -**Question:** "Where should this artist tour next?" - -```bash -# 1. Get top listener cities -python scripts/get_artist_cities.py 3380 - -# 2. Get festivals in those regions -python scripts/list_festivals.py -# Filter by country/region from cities - -# 3. For each city, find which similar artists are touring there -python scripts/get_similar_artists.py 3380 --by-config --audience high --limit 20 -# Check their venues/events (if available) - -# 4. Get YouTube audience by region -python scripts/get_artist_audience.py 3380 --platform youtube - -# 5. Compare playlist reach by country -python scripts/get_artist_playlists.py 3380 --sort followers -# Group by playlist country code -``` - -**Output:** Ranked list of cities by streaming engagement, cross-referenced with festival opportunities and market coverage. - ---- - -## 4. A&R Discovery Workflow - -**Question:** "Find emerging artists in [genre] before they blow up" - -```bash -# 1. Start with a breakout artist in the genre as anchor -python scripts/search_artist.py "Ice Spice" -# ID: 10574889 - -# 2. Find artists similar by musicality (not audience - we want undiscovered) -python scripts/get_similar_artists.py 10574889 --by-config --musicality high --genre high --limit 50 - -# 3. Filter response by career_stage: "undiscovered" or "developing" -# 4. For promising candidates, check their trajectory -python scripts/get_artist_metrics.py --source spotify -python scripts/get_artist_metrics.py --source tiktok - -# 5. Check playlist traction (editorial = label interest) -python scripts/get_artist_playlists.py --editorial - -# 6. Get their insights for AI-generated summary -python scripts/get_artist_insights.py -``` - -**Output:** List of emerging artists with similar sound but smaller audience, sorted by growth velocity. - ---- - -## 5. Catalog Optimization - -**Question:** "Which songs should we push and where?" - -```bash -# 1. Get all artist tracks -python scripts/get_artist_tracks.py 3380 - -# 2. For each track, check playlist placements -python scripts/get_artist_playlists.py 3380 -# Filter by track name to see which songs are playlisted - -# 3. Check which tracks are performing on TikTok -# (from artist top tracks TikTok endpoint) -curl -s "https://api.chartmetric.com/api/artist/3380/top-tracks/tiktok" \ - -H "Authorization: Bearer $TOKEN" - -# 4. Get album performance comparison -python scripts/get_artist_albums.py 3380 -# Compare album release dates to metric spikes - -# 5. Identify underperforming gems: -# - High playlist reach but low streams = discovery issue -# - Low playlist but high TikTok = pitch opportunity -# - Old songs suddenly playlisted = catalog momentum -``` - -**Output:** Track-by-track analysis showing which songs to push on which platforms. - ---- - -## 6. Competitive Roster Analysis - -**Question:** "How does our roster compare to competitor label?" - -```bash -# For each artist on your roster: -# 1. Get their profile and current metrics -python scripts/get_artist.py -python scripts/get_artist_metrics.py --source spotify - -# 2. Find their similar artists (potential competitor roster) -python scripts/get_similar_artists.py --by-config --audience high --genre high - -# 3. Compare playlist reach -python scripts/get_artist_playlists.py --sort followers -python scripts/get_artist_playlists.py --sort followers - -# 4. Compare audience demographics -python scripts/get_artist_audience.py --platform instagram -python scripts/get_artist_audience.py --platform instagram - -# 5. Compare where fans listen -python scripts/get_artist_cities.py -python scripts/get_artist_cities.py -``` - -**Output:** Side-by-side comparison of roster performance, identifying gaps and opportunities. - ---- - -## 7. Viral Song Autopsy - -**Question:** "Why did this song go viral? Can we replicate it?" - -```bash -# 1. Get track details -python scripts/get_track_by_spotify.py "https://open.spotify.com/track/..." -python scripts/get_track.py - -# 2. Get the artist's metrics around release date -python scripts/get_artist_metrics.py --source spotify -python scripts/get_artist_metrics.py --source tiktok - -# 3. Check playlist placements timeline -python scripts/get_artist_playlists.py --since 2025-01-01 --sort added_at - -# 4. Get artist insights (may mention the viral moment) -python scripts/get_artist_insights.py - -# 5. Find which playlists were most impactful -python scripts/get_artist_playlists.py --editorial --sort followers - -# 6. Check if similar artists had similar trajectory -python scripts/get_similar_artists.py --by-config --musicality high -``` - -**Output:** Timeline of the viral moment: What platform first, which playlists amplified, audience demographics that drove sharing. - ---- - -## 8. Market Expansion Scouting - -**Question:** "Which new markets should we focus on?" - -```bash -# 1. Current listener geography -python scripts/get_artist_cities.py 3380 - -# 2. Platform-specific audience breakdown -python scripts/get_artist_audience.py 3380 --platform instagram -python scripts/get_artist_audience.py 3380 --platform youtube -python scripts/get_artist_audience.py 3380 --platform tiktok - -# 3. Find similar artists and their top cities -python scripts/get_similar_artists.py 3380 --by-config --genre high --limit 10 -# For each: -python scripts/get_artist_cities.py - -# 4. Look for cities where similar artists thrive but target artist is weak -# These are expansion opportunities - -# 5. Check playlist coverage in target markets -python scripts/get_artist_playlists.py 3380 -# Filter by playlist country codes -``` - -**Output:** Ranked list of underserved markets where similar artists succeed. - ---- - -## 9. Collaboration Finder - -**Question:** "Which artists should we collaborate with?" - -```bash -# 1. Get similar artists by audience (shared fanbase) -python scripts/get_similar_artists.py 3380 --by-config --audience high --limit 30 - -# 2. Filter by career stage (slightly bigger = good collab target) -# Look for "mid-level" or "mainstream" in response - -# 3. Check genre overlap -python scripts/get_similar_artists.py 3380 --by-config --genre high --musicality high - -# 4. Find overlap in playlist placements -python scripts/get_artist_playlists.py 3380 --editorial -python scripts/get_artist_playlists.py --editorial -# Same playlists = easy collab pitch - -# 5. Check geographic overlap -python scripts/get_artist_cities.py 3380 -python scripts/get_artist_cities.py -``` - -**Output:** Ranked collaboration targets by audience overlap, career stage, and playlist synergy. - ---- - -## 10. Release Strategy Timing - -**Question:** "When should we release, and how should we roll it out?" - -```bash -# 1. Analyze past releases -python scripts/get_artist_albums.py 3380 -python scripts/get_artist_career.py 3380 - -# 2. Check what worked - playlist adds after releases -python scripts/get_artist_playlists.py 3380 --status past --since 2024-01-01 - -# 3. Look at similar artists' successful releases -python scripts/get_similar_artists.py 3380 --by-config --audience high --limit 10 -python scripts/get_artist_albums.py -python scripts/get_artist_career.py - -# 4. Check current playlist momentum -python scripts/get_artist_playlists.py 3380 --editorial --newMusicFriday - -# 5. Identify which platforms are hottest right now -python scripts/get_artist_metrics.py 3380 --source spotify -python scripts/get_artist_metrics.py 3380 --source tiktok -python scripts/get_artist_metrics.py 3380 --source youtube_channel -``` - -**Output:** Release timing recommendation based on historical patterns, playlist cycles, and platform momentum. - ---- - -## Workflow Tips - -1. **Cache tokens** - Multiple calls need valid auth -2. **Rate limit awareness** - Add 1s delay between calls if running many -3. **Save intermediate results** - Pipe to files for analysis: `python scripts/... > results.json` -4. **Cross-reference IDs** - Always use Chartmetric IDs, not Spotify IDs for API calls -5. **Compare timeframes** - Most insights come from comparing metrics over time - ---- - -## Building Your Own Workflows - -The power is in combining: - -| Data Type | Endpoint | Use For | -|-----------|----------|---------| -| Who | `similar-artists`, `relatedartists` | Finding benchmarks, competitors, collaborators | -| Where | `cities`, `audience` | Geographic strategy, tour routing | -| What | `playlists`, `tracks`, `albums` | Content strategy, playlist pitching | -| When | `metrics`, `career` | Timing, trajectory analysis | -| Why | `insights` | AI-generated context | diff --git a/skills/chart-metric/references/api_data.json b/skills/chart-metric/references/api_data.json deleted file mode 100644 index 9c2d697..0000000 --- a/skills/chart-metric/references/api_data.json +++ /dev/null @@ -1,35054 +0,0 @@ -[ - { - "type": "get", - "url": "/api/album/:id", - "title": "Metadata", - "version": "1.0.0", - "name": "GetAlbumMetadata", - "description": "

Get album's metadata given Chartmetric ID.
This data will be cached daily.

", - "group": "Album", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric album ID

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "upc", - "description": "

Album UPC

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Album Name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Album image url

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "release_date", - "description": "

Album release date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "label", - "description": "

Album Label

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "spotify_popularity", - "description": "

Spotify popularity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_label", - "description": "

Chartmetric label

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "description", - "description": "

Album description

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "created_at", - "description": "

Album created date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "modified_at", - "description": "

Album modified date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_album", - "description": "

Chartmetric Album Id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "num_track", - "description": "

Number of track

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_album_ids", - "description": "

Spotify album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "itunes_album_ids", - "description": "

iTunes album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "deezer_album_ids", - "description": "

Deezer album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artists", - "description": "

Chartmetric Artist info

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.cm_artist", - "description": "

Artist id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.image_url", - "description": "

Artist image url

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "cm_statistics", - "description": "" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.sp_popularity", - "description": "

Spotify popularity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_sp_editorial_playlists", - "description": "

Number of Spotify editorial playlists

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_sp_playlists", - "description": "

Number of Spotify playlists

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.sp_playlist_total_reach", - "description": "

Number of Spotify playlists total reach

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.sp_editorial_playlist_total_reach", - "description": "

Number of Spotify editorial playlists total reach

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"id\": null,\n \"upc\": \"3614599568697\",\n \"name\": \"Deja Fu\",\n \"image_url\": \"https://i.scdn.co/image/250c66bd1cfd8e1177055c998b43feac5a62ed0d\",\n \"release_date\": \"2016-06-22T07:00:00.000Z\",\n \"label\": \"Ranka Kustannus Oy\",\n \"spotify_popularity\": 2,\n \"cm_label\": 44035,\n \"description\": null,\n \"created_at\": null,\n \"modified_at\": null,\n \"cm_album\": 815685,\n \"num_track\": \"1\",\n \"spotify_album_ids\": [\n \"3hpphhVJM2QTUMYuXT7nw4\"\n ],\n \"itunes_album_ids\": null,\n \"deezer_album_ids\": null,\n \"artists\": [\n {\n \"cm_artist\": 256101,\n \"name\": \"Blind Channel\",\n \"image_url\": \"https://i.scdn.co/image/99ff63e0299ffe1faa62d90056f6ca59aa2f3443\"\n }\n ],\n \"cm_statistics\": {\n \"sp_popularity\": 2,\n \"num_sp_editorial_playlists\": 1,\n \"num_sp_playlists\": 19,\n \"sp_playlist_total_reach\": 10757,\n \"sp_editorial_playlist_total_reach\": 0\n }\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/album/815685/", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/album.docs.ts", - "groupTitle": "Album" - }, - { - "type": "get", - "url": "/api/album/:id/tracks", - "title": "Tracks", - "version": "1.0.0", - "name": "GetAlbumTracks", - "group": "Album", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric album ID

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric's Track ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track's name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Track image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isrc", - "description": "

Track ISRC

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "description", - "description": "

Track description

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "created_at", - "description": "

Track created date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "modified_at", - "description": "

Track modified date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tags", - "description": "

Comma separated list of genres for the track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_track", - "description": "

Chartmetric's Track ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "cm_artist", - "description": "

Chartmetric Artist IDs.

Note: The ordering of the artist related fields below artist_names, code2s, artist_images, and artist_covers corresponds to that of the artist IDs in cm_artist.
Prefer the data in the field cm_artists.id. This field will be deprecated in the future.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist_names", - "description": "

Artist names

Note: Prefer the data in the field cm_artists.name. This field will be deprecated in the future.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "code2s", - "description": "

Track country codes

Note: Prefer the data in the field cm_artists.code2. This field will be deprecated in the future.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist_images", - "description": "

Artist image urls

Note: Prefer the data in the field cm_artists.image_url. This field will be deprecated in the future.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist_covers", - "description": "

Artist covers

Note: Prefer the data in the field cm_artists.cover_url. This field will be deprecated in the future.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_track_ids", - "description": "

Spotify track ids. Ids are sorted by popularity score.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_album_ids", - "description": "

Spotify album ids

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "spotify_duration_ms", - "description": "

Spotify duration

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "itunes_track_ids", - "description": "

iTunes track ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "itunes_album_ids", - "description": "

iTunes album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "storefronts", - "description": "

Storefronts

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "deezer_track_ids", - "description": "

Deezer track ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "deezer_album_ids", - "description": "

Deezer album ids

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "deezer_duration", - "description": "

Deezer duration

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "amazon_track_ids", - "description": "

Amazon track ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "amazon_album_ids", - "description": "

Amazon album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_ids", - "description": "

Album ids.

Note: The ordering of the album related fields below album_names, album_upc, album_label, and release_dates corresponds to that of the album IDs in album_ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_names", - "description": "

Album names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_upc", - "description": "

Album UPC codes

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_label", - "description": "

Album labels

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "release_dates", - "description": "

Album release dates

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "cm_artists", - "description": "

Array of objects with track artists data

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_artists.id", - "description": "

Artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_artists.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_artists.code2", - "description": "

Artist country

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_artists.image_url", - "description": "

Artist image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_artists.cover_url", - "description": "

Artist cover url

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "cm_statistics", - "description": "

Statistics

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_sp_editorial_playlists", - "description": "

Number of Spotify editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_sp_playlists", - "description": "

Number of Spotify playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.sp_playlist_total_reach", - "description": "

Sum of followers from all Spotify playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_am_editorial_playlists", - "description": "

Number of Apple Music editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_am_playlists", - "description": "

Number of Apple Music playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_de_editorial_playlists", - "description": "

Number of Deezer editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_de_playlists", - "description": "

Number of Deezer playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_az_editorial_playlists", - "description": "

Number of Amazon editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_az_playlists", - "description": "

Number of Amazon playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.de_playlist_total_reach", - "description": "

Sum of fans from all Deezer playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_yt_editorial_playlists", - "description": "

Number of YouTube editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_yt_playlists", - "description": "

Number of YouTube playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.yt_playlist_total_reach", - "description": "

Number of YouTube playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.cm_track", - "description": "

Chartmetric track ID

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.sp_popularity", - "description": "

Number of Spotify followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_tt_videos", - "description": "

TikTok videos count

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.genius_page_views", - "description": "

Number of Genius page views

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.shazam_counts", - "description": "

Number of Shazam plays

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.tiktok_counts", - "description": "

Number of TikTok plays

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.youtube_views", - "description": "

Number of YouTube views

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 22396642,\n \"name\": \"break up with your girlfriend, i'm bored\",\n \"image_url\": \"https://api.deezer.com/album/86773062/image\",\n \"isrc\": \"USUM71900409\",\n \"description\": null,\n \"created_at\": null,\n \"modified_at\": \"2020-07-03T05:46:13.804Z\",\n \"tags\": \"Pop,Music\",\n \"cm_track\": 22396642,\n \"cm_artist\": [\n 3963\n ],\n \"artist_names\": [\n \"Ariana Grande\"\n ],\n \"code2s\": [\n \"US\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/b1dfbe843b0b9f54ab2e588f33e7637d2dab065a\"\n ],\n \"artist_covers\": [\n \"https://i.scdn.co/image/4599ff6a6b96d54df41e97a484605f0e517ba278\"\n ],\n \"spotify_track_ids\": [\n \"4kV4N9D1iKVxx1KLvtTpjS\",\n \"09Yv7hLbsO0cBhQzFiJqeg\"\n ],\n \"spotify_album_ids\": [\n \"2fYhqwDWXjbpjaIJPEfKFw\",\n \"6KpJNh4s2moJHgChy3nNL8\"\n ],\n \"spotify_duration_ms\": 190440,\n \"itunes_track_ids\": [\n \"1450330687\"\n ],\n \"itunes_album_ids\": [\n \"1450330588\"\n ],\n \"storefronts\": [\n \"LA\"\n ],\n \"deezer_track_ids\": [\n \"629899862\"\n ],\n \"deezer_album_ids\": [\n 86773062\n ],\n \"deezer_duration\": 190,\n \"amazon_track_ids\": [\n \"B082XHFBS6\",\n \"B07N31H6YJ\"\n ],\n \"amazon_album_ids\": [\n \"B082XGFGGN\",\n \"B07N31J22R\"\n ],\n \"album_ids\": [\n 3532913,\n 3533190,\n 4678085\n ],\n \"album_names\": [\n \"thank u, next\",\n \"thank u, next\",\n \"Mega Hits Summer 2019\"\n ],\n \"album_upc\": [\n \"602577490385\",\n \"602577464980\",\n \"886447802601\"\n ],\n \"album_label\": [\n \"Republic Records\",\n \"Republic Records\",\n \"Sony Music Entertainment\"\n ],\n \"release_dates\": [\n \"2019-02-08T08:00:00.000Z\",\n \"2019-02-08T08:00:00.000Z\",\n \"2019-07-26T07:00:00.000Z\"\n ],\n \"cm_artists\": [\n {\n \"id\": 3963,\n \"name\": \"Ariana Grande\",\n \"code2\": \"US\",\n \"image_url\": \"https://i.scdn.co/image/b1dfbe843b0b9f54ab2e588f33e7637d2dab065a\",\n \"cover_url\": \"https://i.scdn.co/image/4599ff6a6b96d54df41e97a484605f0e517ba278\"\n },\n ...\n ],\n \"cm_statistics\": {\n \"num_sp_playlists\": 3556,\n \"num_sp_editorial_playlists\": 57,\n \"num_am_playlists\": 49,\n \"num_am_editorial_playlists\": 4,\n \"num_de_playlists\": 11,\n \"num_de_editorial_playlists\": 2,\n \"num_az_playlists\": 36,\n \"num_az_editorial_playlists\": 36,\n \"num_yt_playlists\": 12,\n \"num_yt_editorial_playlists\": 3,\n \"sp_playlist_total_reach\": \"10760959\",\n \"de_playlist_total_reach\": \"350115\",\n \"yt_playlist_total_reach\": \"6404331\",\n \"cm_track\": 22396642,\n \"sp_popularity\": 80,\n \"num_tt_videos\": 69100,\n \"genius_page_views\": null,\n \"shazam_counts\": 1896424,\n \"tiktok_counts\": 69100,\n \"youtube_views\": \"369742364\"\n }\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/album/3533190/tracks", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/album.docs.ts", - "groupTitle": "Album" - }, - { - "type": "get", - "url": "/api/album/:id/:platform/:stats", - "title": "Stats", - "name": "GetCurrentPopularityByAlbum", - "version": "1.0.0", - "group": "Album", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric album ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify" - ], - "optional": false, - "field": "platform", - "description": "

Streaming platform type

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "followers" - ], - "optional": false, - "field": "stat", - "description": "

Platform stat

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "since", - "description": "

beginning date in ISO date format (e.g. "2020-09-21")

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

end date, in ISO date format (e.g. "2020-09-23")

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "value", - "description": "

Statistic from given service (popularity for Spotify)

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "timestp", - "description": "

The time this statistic was achieved

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"value\": 97,\n \"timestp\": \"2020-09-20T07:00:00.000Z\"\n },\n {\n \"value\": 97,\n \"timestp\": \"2020-09-21T07:00:00.000Z\"\n },\n {\n \"value\": 97,\n \"timestp\": \"2020-09-22T07:00:00.000Z\"\n },\n {\n \"value\": 97,\n \"timestp\": \"2020-09-23T07:00:00.000Z\"\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/album/6382276/spotify/followers?since=2020-09-20&until=2020-09-23", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/album.docs.ts", - "groupTitle": "Album" - }, - { - "type": "get", - "url": "/api/album/:id/:platform/:status/playlists", - "title": "Playlists", - "version": "1.0.0", - "name": "GetPlaylistsByAlbums", - "description": "

Get current or past playlists for an album.

Parameter options for each platform

Each platform supports different parameters. Valid parameters are provided in the below table for your convenience:

Parameters spotify applemusic deezer amazon
editorial
editorialBrand
personalized
deezerPartner
chart
thisIs
hundredPercent
newMusicFriday
radio
fullyPersonalized
brand
majorCurator
musicBrand
nonMusicBrand
popularIndie
indie
audiobook
personalityArtist

Allowed Parameters Summary:

  • spotify: editorial, personalized, chart, thisIs, newMusicFriday, radio, fullyPersonalized, brand, majorCurator, popularIndie, indie, audiobook
  • applemusic: editorial, editorialBrand, chart, radio, musicBrand, nonMusicBrand, indie, personalityArtist
  • deezer: editorial, deezerPartner, chart, hundredPercent, brand, majorCurator, popularIndie, indie
  • amazon: No allowed flags

Important: When no parameters specified, this API can return an empty array. API users should specify parameters explicitly to get data.

Parameters are not mutually exclusive.

Specify other parameters as FALSE to retrieve data only specific to one parameter. (ex. only newMusicFriday)

", - "group": "Album", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric album ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "applemusic", - "deezer", - "amazon", - "youtube" - ], - "optional": false, - "field": "platform", - "description": "

Streaming platform type

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "current", - "past" - ], - "optional": false, - "field": "status", - "description": "

Playlist status

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "since", - "defaultValue": "180 days ago", - "description": "

beginning of the added/removed date, in ISO date format (e.g. "2020-09-20"). The default value will be 6 months prior to the date of call.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

end of the added/removed date, in ISO date format (e.g. "2020-09-23").

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "indie", - "description": "

If the playlist was curated by major labels

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "editorial", - "defaultValue": "true", - "description": "

If true, the response includes playlists curated by the platform (e.g. Spotify Editorial Playlists, Apple Music Editorial Playlists, Deezer Editorial Playlists). API results match the app only when this parameter is set to true. Spotify includes "personalized" playlists when editorial is set to true.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "majorCurator", - "description": "

If true, the response includes playlists curated by major curators (e.g. Filtr Colombia, Digster, Deezer MENA editor). API results match the app only when this parameter is set to true.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "newMusicFriday", - "description": "

A curated selection of Spotify's most anticipated new tracks of the week.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "editorialBrand", - "description": "

If true, includes playlists curated under specific editorial brands or themes by Apple Music's editorial team.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "personalized", - "description": "

If true, the response includes personalized playlists created based on user's listening habits and preferences.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "deezerPartner", - "description": "

If true, the response includes playlists curated by Deezer partners.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "chart", - "description": "

If true, the response includes playlists based on music charts.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "thisIs", - "description": "

If true, the response includes "This Is" playlists curated by Spotify.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "hundredPercent", - "description": "

If true, the response includes "100&" playlists curated by Deezer

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "radio", - "description": "

If true, the response includes playlists from radio stations.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "fullyPersonalized", - "description": "

If true, the response includes fully personalized playlists curated by Spotify based on the user's listening behavior and preferences

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "brand", - "description": "

If true, the response includes playlists related to brands.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "musicBrand", - "description": "

If true, the response includes playlists curated under specific music brands by Apple Music's editorial team.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "nonMusicBrand", - "description": "

If true, the response includes playlists curated under specific non-music brands by Apple Music's editorial team

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "popularIndie", - "description": "

If true, the response includes playlists featuring popular indie tracks.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "audiobook", - "description": "

If true, the response includes audiobook playlists curated by Spotify. These playlists feature audiobook content.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "personalityArtist", - "description": "

If true, the response includes playlists curated under specific personality artists by Apple Music's editorial team

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "playlist", - "description": "

Playlist details

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.playlist_id", - "description": "

Spotify's playlist ID (from URI)

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.track_id", - "description": "

Track ID

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.position", - "description": "

The position of the track in the playlist

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "playlist.added_at", - "description": "

Added at date

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.period", - "description": "

Period

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.name", - "description": "

Playlist Title

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.description", - "description": "

Playlist description

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.image_url", - "description": "

Playlist cover image URL

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "playlist.sys_last_updated", - "description": "

System last updated date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "playlist.last_updated", - "description": "

Last update date for this playlist

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "playlist.personalized", - "description": "

Personalized status

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.code2", - "description": "

Country

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.owner_name", - "description": "

Curator name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.owner_id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "playlist.editorial", - "description": "

If this playlist is curated by an official editorial team. Applies only to Spotify, Apple Music, and Deezer platforms.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "playlist.tags", - "description": "

Playlist tags

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.tags.id", - "description": "

Chartmetric ID of tags

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.tags.name", - "description": "

Tags name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.followers", - "description": "

Most recent follower count

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.num_track", - "description": "

Total number of tracks in playlist

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.fdiff_week", - "description": "

Difference in number of followers compared to previous week, indicating weekly follower growth

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.fdiff_month", - "description": "

Difference in number of followers compared to previous month, indicating monthly follower growth

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.catalog", - "description": "

Playlist catalog

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "playlist.active_ratio", - "description": "

Active ratio of playlist

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "track", - "description": "

Track info

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track.id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.isrc", - "description": "

Track's isrc

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.spotify_album_id", - "description": "

Track's Spotify album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.image_url", - "description": "

Track cover image URL

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track.spotify_popularity", - "description": "

The track's Spotify popularity stat

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track.cm_track", - "description": "

Track's Chartmetric ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_artist_names", - "description": "

List of Spotify artist names

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.track_genre", - "description": "

List of genre for the track

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.cm_artist", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.artist_names", - "description": "

Names of artists

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.code2s", - "description": "

Country code of artist

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.artist_images", - "description": "

Image URL of artists

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.artist_covers", - "description": "

Cover image of artists

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_artist_ids", - "description": "

List of Spotify artist IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_track_ids", - "description": "

List of track IDs from Spotify

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_album_ids", - "description": "

List of album IDs from Spotify

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_duration_ms", - "description": "

Spotify duration in milliseconds

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_ids", - "description": "

Album IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_names", - "description": "

Name of album

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_upc", - "description": "

Album upc

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.release_dates", - "description": "

Release date of track

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"playlist\": {\n \"playlist_id\": \"37i9dQZF1DWWMOmoXKqHTD\",\n \"track_id\": 17340502,\n \"peak_position\": 1,\n \"position\": 22,\n \"cm_track\": 27222889,\n \"fdiff_month\": 36241,\n \"added_at\": \"2023-07-11\",\n \"period\": 105,\n \"followers\": 10707440,\n \"track\": \"Watermelon Sugar\",\n \"name\": \"Songs to Sing in the Car\",\n \"playlist_image_url\": \"https://i.scdn.co/image/ab67706f00000003ffa215be1a4c64e3cbf59d1e\",\n \"code2\": \"US\",\n \"curator_name\": \"Spotify\",\n \"id\": 1644599,\n \"description\": \"\",\n \"image_url\": \"https://i.scdn.co/image/ab67706f00000003ffa215be1a4c64e3cbf59d1e\",\n \"sys_last_updated\": \"2023-10-24T00:00:00.000Z\",\n \"last_updated\": \"2023-10-21T09:09:20.000Z\",\n \"personalized\": true,\n \"owner_name\": \"Spotify\",\n \"owner_id\": 2,\n \"user_id\": \"spotify\",\n \"editorial\": false,\n \"tags\": [\n {\n \"id\": 462882,\n \"name\": \"Pop\"\n },\n {\n \"id\": 462884,\n \"name\": \"Rock\"\n },\n {\n \"id\": 462889,\n \"name\": \"R&B/Soul\"\n }\n ],\n \"num_track\": 100,\n \"fdiff_week\": 11408,\n \"catalog\": \"catalogue \",\n \"active_ratio\": 0\n },\n \"track\": {\n \"id\": 17340502,\n \"name\": \"Watermelon Sugar\",\n \"isrc\": \"USSM11912587\",\n \"spotify_track_id\": \"6UelLqGlWMcVH1E5c4H7lY\",\n \"spotify_album_id\": \"7xV2TzoaVc0ycW7fwBwAml\",\n \"image_url\": \"https://api.deezer.com/album/119400582/image\",\n \"spotify_popularity\": 89,\n \"cm_track\": 27222889,\n \"spotify_artist_names\": [\n \"Harry Styles\"\n ],\n \"track_genre\": \"dance pop,pop,post-teen pop\",\n \"cm_artist\": [\n 558681\n ],\n \"artist_names\": [\n \"Harry Styles\"\n ],\n \"code2s\": [\n \"GB\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/ab67616100005174f7db7c8ede90a019c54590bb\"\n ],\n \"artist_covers\": [\n null\n ],\n \"spotify_artist_ids\": [\n \"6KImCVD70vtIoJWnq6nGn3\"\n ],\n \"spotify_track_ids\": [\n \"6UelLqGlWMcVH1E5c4H7lY\"\n ],\n \"spotify_album_ids\": [\n \"7xV2TzoaVc0ycW7fwBwAml\"\n ],\n \"spotify_duration_ms\": 174000,\n \"album_ids\": [\n 5370467\n ],\n \"album_names\": [\n \"Watermelon Sugar\"\n ],\n \"album_upc\": [\n \"886448132844\"\n ],\n \"album_label\": [\n \"Columbia\"\n ],\n \"release_dates\": [\n \"2019-12-13\"\n ],\n \"composer_name\": \"Harry Styles, Tyler Samuel Johnson\",\n \"album\": [\n {\n \"id\": 5370467,\n \"label\": \"Columbia\",\n \"name\": \"Watermelon Sugar\",\n \"release_date\": \"2019-11-17\",\n \"upc\": \"886448132844\"\n }\n ],\n \"duration_ms\": 174000,\n \"explicit\": false,\n \"score\": 898,\n \"songwriter\": [\n \"Harry Styles\",\n \"Tyler Samuel Johnson\"\n ],\n \"artists\": [\n {\n \"code2\": \"GB\",\n \"id\": 558681,\n \"name\": \"Harry Styles\"\n }\n ]\n }\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (only newMusicFriday):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"playlist\": {\n \"playlist_id\": \"37i9dQZF1DX4JAvHpjipBk\",\n \"track_id\": 128029173,\n \"peak_position\": 5,\n \"position\": 5,\n \"cm_track\": 105004575,\n \"fdiff_month\": 25789,\n \"added_at\": \"2023-07-14\",\n \"period\": 6,\n \"followers\": 4149157,\n \"track\": \"Seven (feat. Latto) (Explicit Ver.)\",\n \"name\": \"New Music Friday\",\n \"playlist_image_url\": \"https://i.scdn.co/image/ab67706f00000003ff2269719fd22a3d75377924\",\n \"code2\": \"US\",\n \"curator_name\": \"Spotify\",\n \"id\": 1645898,\n \"description\": \"New music from Billie Eilish, KAROL G, Post Malone, Troye Sivan, Lil Tjay & Summer Walker, Dan + Shay and more! \",\n \"image_url\": \"https://i.scdn.co/image/ab67706f00000003ff2269719fd22a3d75377924\",\n \"sys_last_updated\": \"2023-07-20T00:00:00.000Z\",\n \"last_updated\": \"2023-07-14T11:11:57.000Z\",\n \"personalized\": false,\n \"owner_name\": \"Spotify\",\n \"owner_id\": 2,\n \"user_id\": \"spotify\",\n \"editorial\": false,\n \"num_track\": 100,\n \"fdiff_week\": 16072,\n \"catalog\": \"frontline\",\n \"active_ratio\": 1\n },\n \"track\": {\n \"id\": 128029173,\n \"name\": \"Seven (feat. Latto) (Explicit Ver.)\",\n \"isrc\": \"USA2P2330093\",\n \"spotify_track_id\": \"7x9aauaA9cu6tyfpHnqDLo\",\n \"spotify_album_id\": \"53985D8g3JcGBoULSOYYKX\",\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b273bf5cce5a0e1ed03a626bdd74\",\n \"spotify_popularity\": 95,\n \"cm_track\": 105004575,\n \"spotify_artist_names\": [\n \"Jung Kook\",\n \"Latto\"\n ],\n \"track_genre\": \"deep pop r&b,trap queen\",\n \"cm_artist\": [\n 8467662,\n 241165\n ],\n \"artist_names\": [\n \"Jung Kook\",\n \"Latto\"\n ],\n \"code2s\": [\n \"KR\",\n \"US\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/ab676161000051741fe20af411fd6273a25f664e\",\n \"https://i.scdn.co/image/ab67616100005174149677e9b0d9ef7b229499d9\"\n ],\n \"artist_covers\": [\n null,\n null\n ],\n \"spotify_artist_ids\": [\n \"6HaGTQPmzraVmaVxvz6EUc\",\n \"3MdXrJWsbVzdn6fe5JYkSQ\"\n ],\n \"spotify_track_ids\": [\n \"7x9aauaA9cu6tyfpHnqDLo\"\n ],\n \"spotify_album_ids\": [\n \"53985D8g3JcGBoULSOYYKX\"\n ],\n \"spotify_duration_ms\": 184400,\n \"album_ids\": [\n 28241427\n ],\n \"album_names\": [\n \"Seven (feat. Latto)\"\n ],\n \"album_upc\": [\n \"196922462870\"\n ],\n \"album_label\": [\n \"BIGHIT MUSIC\"\n ],\n \"release_dates\": [\n \"2023-07-14T00:00:00.000Z\"\n ],\n \"composer_name\": null,\n \"album\": [\n {\n \"id\": 28241427,\n \"label\": \"BIGHIT MUSIC\",\n \"name\": \"Seven (feat. Latto)\",\n \"release_date\": \"2023-07-14\",\n \"upc\": \"196922462870\"\n }\n ],\n \"duration_ms\": 184400,\n \"explicit\": true,\n \"score\": 2659,\n \"artists\": [\n {\n \"code2\": \"KR\",\n \"id\": 8467662,\n \"name\": \"Jung Kook\"\n },\n {\n \"code2\": \"US\",\n \"id\": 241165,\n \"name\": \"Latto\"\n }\n ]\n }\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/album/5449730/spotify/current/playlists?editorial=true&editorialBrand=false&personalized=true&deezerPartner=false&chart=false&thisIs=false&hundredPercent=false&newMusicFriday=true&radio=false&fullyPersonalized=false&brand=true&majorCurator=true&musicBrand=false&nonMusicBrand=false&popularIndie=true&indie=true&audiobook=false", - "type": "curl" - }, - { - "title": "Example usage (only newMusicFriday):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/album/28104961/spotify/current/playlists?indie=false&editorial=false&majorCurator=false&newMusicFriday=true", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/album.docs.ts", - "groupTitle": "Album" - }, - { - "type": "get", - "url": "/api/album/:id/:type/charts", - "title": "Charts", - "version": "1.0.0", - "name": "getAlbumCharts", - "description": "

This API returns the charts for this album. For example, you can retrieve chart records from Apple Music Album Charts or iTunes Album Charts.

", - "group": "Album", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "applemusic", - "itunes", - "amazon" - ], - "optional": false, - "field": "type", - "description": "" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Album Chartmetric ID

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "since", - "defaultValue": "180 days ago", - "description": "

Start date in ISO date format (e.g. 2020-03-01).

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

End date in ISO date format (e.g. 2020-09-01). This date may be up to 365 days away from the since date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "itunes_album_id", - "description": "

iTunes Album Id

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

Chart position

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "added_at", - "description": "

Date of data recorded

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "code2", - "description": "

Charting country code

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Charting genre

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "gnp", - "description": "

Album GNP

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "pre_rank", - "description": "

Latest Previous Chart Position

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "peak_rank", - "description": "

Best Chart Position

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "peak_date", - "description": "

Date of best Chart Position

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Album name

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "release_date", - "description": "

Album release date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "copyright", - "description": "

Album copyright

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "num_track", - "description": "

Number of tracks

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "upc", - "description": "

Album UPC

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "description", - "description": "

Album description

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "is_single", - "description": "

Is single album

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artwork_url", - "description": "

Album artwork url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genres", - "description": "

Album genres

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "created_at", - "description": "

Album created date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "modified_at", - "description": "

Album modified date

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_album", - "description": "

Chartmetric album id

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "storefronts", - "description": "

Storefronts

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "itunes_album_ids", - "description": "

iTunes Album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artists", - "description": "

Artists info

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.itunes_artist_id", - "description": "

iTunes Artist id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.cm_artist", - "description": "

Chartmetric artist id

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Total length of data

" - } - ] - }, - "examples": [ - { - "title": "Success-Response", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"data\": [\n {\n \"itunes_album_id\": \"1450330588\",\n \"rank\": 84,\n \"added_at\": \"2020-03-04T08:00:00.000Z\",\n \"code2\": \"US\",\n \"genre\": \"All Genres\",\n \"gnp\": \"8510700.00\",\n \"pre_rank\": 80,\n \"peak_rank\": 1,\n \"peak_date\": \"2019-02-08T08:00:00.000Z\",\n \"id\": 3058232,\n \"name\": \"thank u, next\",\n \"release_date\": \"2019-02-08T08:00:00.000Z\",\n \"copyright\": \"℗ 2019 Republic Records, a division of UMG Recordings, Inc.\",\n \"num_track\": \"12\",\n \"upc\": \"602577464980\",\n \"description\": null,\n \"is_single\": false,\n \"artwork_url\": \"https://is5-ssl.mzstatic.com/image/thumb/Music124/v4/4e/26/d5/4e26d5f3-2d62-1b5c-689a-e8de4e6f3797/00602577465284.rgb.jpg/160x160bb.jpeg\",\n \"label\": \"Republic Records\",\n \"genres\": \"Pop,Music\",\n \"created_at\": null,\n \"modified_at\": \"2020-09-17T03:10:01.823Z\",\n \"cm_album\": 3533190,\n \"storefronts\": [\n \"AU\",\n \"JP\",\n \"LA\",\n \"NO\",\n \"US\"\n ],\n \"itunes_album_ids\": [\n \"1450330588\"\n ],\n \"artists\": [\n {\n \"itunes_artist_id\": 412778295,\n \"name\": \"Ariana Grande\",\n \"cm_artist\": 3963\n }\n ]\n },\n ...\n ],\n \"length\": 175\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/album/3533190/applemusic/charts?since=2020-03-01&until=2020-03-04", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/album.docs.ts", - "groupTitle": "Album" - }, - { - "type": "get", - "url": "/api/album/:type/:id/get-ids", - "title": "Get Album IDs", - "version": "1.0.0", - "name": "getAlbumsIDs", - "description": "

Get all Albums IDs using a specific id type.

", - "group": "Album", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "chartmetric", - "upc", - "spotify", - "itunes", - "deezer", - "amazon" - ], - "optional": false, - "field": "type", - "description": "

Use 'chartmetric' (for cm_album)

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Specific Type ID

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_album", - "description": "

Album Id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album_name", - "description": "

Album Name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "upc", - "description": "

Album UPC

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "spotify_album_id", - "description": "

Spotify Album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "itunes_album_id", - "description": "

iTunes Album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "deezer_album_id", - "description": "

Deezer Album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "amazon_album_id", - "description": "

Amazon Album ID (ASIN)

" - } - ] - }, - "examples": [ - { - "title": "Success-Response", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"cm_album\": 815685,\n \"album_name\": \"Deja Fu\",\n \"upc\": \"3614599568697\",\n \"spotify_album_id\": \"3hpphhVJM2QTUMYuXT7nw4\",\n \"itunes_album_id\": \"1126469777\",\n \"deezer_album_id\": null,\n \"amazon_album_id\": \"B01HDU9RN0\"\n },\n {\n \"cm_album\": 815685,\n \"album_name\": \"Deja Fu\",\n \"upc\": \"3614599568697\",\n \"spotify_album_id\": \"3hpphhVJM2QTUMYuXT7nw4\",\n \"itunes_album_id\": \"1126469777\",\n \"deezer_album_id\": null,\n \"amazon_album_id\": \"B01HFW1FHW\"\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage (Chartmetric):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/album/chartmetric/815685/get-ids", - "type": "curl" - }, - { - "title": "Example usage (Spotify):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/album/spotify/3hpphhVJM2QTUMYuXT7nw4/get-ids", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/album.docs.ts", - "groupTitle": "Album" - }, - { - "type": "get", - "url": "/api/artist/anr/by/playlists", - "title": "ANR (By playlists)", - "version": "1.1.0", - "name": "GetArtistANRListsByPlaylists", - "description": "

Get Artist ANR by given Playlist

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "followers_total_reach_diff_week", - "followers_total_reach_diff_week_percent" - ], - "optional": false, - "field": "sortBy", - "description": "

Column to sort by

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify" - ], - "optional": false, - "field": "streamingType", - "description": "

Streaming Platform Type

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

Chartmetric Rank.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Artist ID.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "num_playlists", - "description": "

count of playlist.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "num_tracks", - "description": "

count of track.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "followers_total_reach", - "description": "

followers total reach.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "followers_total_reach_diff_week", - "description": "

weekly follower growth.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "followers_total_reach_diff_week_percent", - "description": "

The percentage of weekly follower growth.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "popularity", - "description": "

Number of Spotify popularity.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "popularity_diff_week", - "description": "

weekly popularity growth.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "popularity_diff_week_percent", - "description": "

The percentage of weekly popularity growth.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "followers", - "description": "

Number of Spotify followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "followers_diff_week", - "description": "

weekly followers growth.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "followers_diff_week_percent", - "description": "

The percentage of weekly followers growth.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "listeners", - "description": "

Number of Spotify listeners.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "listeners_diff_week", - "description": "

weekly listeners growth.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "listeners_diff_week_percent", - "description": "

The percentage of weekly listeners growth.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "timestp", - "description": "

time stamp.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Artist name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Artist image.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isni", - "description": "

isni of Artist.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "code2", - "description": "

Country codes.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "hometown_city", - "description": "

hometown city of Artist.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "current_city", - "description": "

current city of Artist.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_followers", - "description": "

Number of Spotify followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_popularity", - "description": "

Number of Spotify followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_monthly_listeners", - "description": "

Number of Spotify followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "deezer_fans", - "description": "

Number of Spotify followers.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "tags", - "description": "

Genres of Artist.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_artist_ids", - "description": "

Spotify artist IDs.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "itunes_artist_ids", - "description": "

iTunes Artist IDs.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "deezer_artist_ids", - "description": "

Deezer Artist IDs.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_artist_rank", - "description": "

Chartmetric artist rank.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "amazon_artist_ids", - "description": "

Amazon Artist IDs.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "releases", - "description": "

album release info.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "releases.cm_album", - "description": "

Chartmetric Album ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "releases.album_name", - "description": "

Album name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "releases.release_date", - "description": "

Album release date.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "releases.upc", - "description": "

Album UPC.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "releases.label", - "description": "

Album label.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "releases.image_url", - "description": "

Album image url.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "genres", - "description": "

genres List of genres.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"rank\": \"1\",\n \"id\": 53722,\n \"num_playlists\": 17,\n \"num_tracks\": 1,\n \"followers_total_reach\": 1596657,\n \"followers_total_reach_diff_week\": 236916,\n \"followers_total_reach_diff_week_percent\": 0.174236122908701,\n \"popularity\": 43,\n \"popularity_diff_week\": 0,\n \"popularity_diff_week_percent\": 0,\n \"followers\": 2570,\n \"followers_diff_week\": 2,\n \"followers_diff_week_percent\": 0.000778816199376947,\n \"listeners\": 182354,\n \"listeners_diff_week\": 1829,\n \"listeners_diff_week_percent\": 0.0101315607256613,\n \"timestp\": \"2020-02-05T08:00:00.000Z\",\n \"name\": \"Tharyk\",\n \"image_url\": \"https://i.scdn.co/image/481216e68d8cdc2dad2ba9c64e4d9fcc98b5192a\",\n \"isni\": null,\n \"code2\": null,\n \"hometown_city\": null,\n \"current_city\": null,\n \"sp_followers\": 2570,\n \"sp_popularity\": 43,\n \"sp_monthly_listeners\": 182354,\n \"deezer_fans\": 173,\n \"tags\": null,\n \"spotify_artist_ids\": [\n \"0l4NszHlXslt1dBt4Y28f7\"\n ],\n \"itunes_artist_ids\": [\n 1053123426\n ],\n \"deezer_artist_ids\": [\n \"9308498\"\n ],\n \"cm_artist_rank\": 31792,\n \"amazon_artist_ids\": null,\n \"releases\": [\n {\n \"cm_album\": 469946,\n \"album_name\": \"Mi Destino\",\n \"release_date\": \"2015-12-04T08:00:00.000Z\",\n \"upc\": \"859716151359\",\n \"label\": \"G+T Music\",\n \"image_url\": \"https://i.scdn.co/image/b08d575f8af8f5b3ccff6250103f878163be7cf7\"\n },\n {\n \"cm_album\": 5562463,\n \"album_name\": \"Malas Intenciones\",\n \"release_date\": \"2020-01-03T08:00:00.000Z\",\n \"upc\": \"8433391920848\",\n \"label\": \"ginTONIC Records\",\n \"image_url\": \"https://i.scdn.co/image/ce08ca2f8589c4387ec9ea1435b6644a9012e743\"\n }\n ],\n \"genres\": []\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer ACCESS_KEY' https://api.chartmetric.com/api/artist/anr/by/playlists?limit=100&sortBy=followers_total_reach_diff_week&streamingType=spotify", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/anr/by/social-index", - "title": "ANR (By Social Index)", - "version": "1.1.0", - "name": "GetArtistANRListsBySocialIndex", - "description": "

Get Artist ANR by Social Index

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify_popularity", - "spotify_followers", - "spotify_monthly_listeners", - "twitter_followers", - "instagram_followers", - "wiki_views", - "soundcloud_followers", - "tiktok_followers", - "youtube_channel_views" - ], - "optional": false, - "field": "sortBy", - "description": "

column to sort by

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

the offset of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "US", - "JP", - "..." - ], - "optional": true, - "field": "code2", - "description": "

2-Letter Country Code. (Full List in Introduction).

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "tagIds", - "description": "

parent genre ids

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "subTagIds", - "description": "

Child genre ids.
Note: This parameter is deprecated and has no effect on the response. It will be removed after 06-30-2023.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "maxSpotifyFollowers", - "description": "

The max number of Spotify followers to filter by.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "sortOrderDesc", - "defaultValue": "true", - "description": "

Whether to sort in descending order.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "recentReleaseWithin", - "description": "

Filter on artists who have their first release within a certain time period, in days.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "latestReleaseWithin", - "description": "

Filter on artists who have their latest release within a certain time period, in days.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Artist name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "code2", - "description": "

Country of the artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Artist image url.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "tags", - "description": "

Genres of Artist.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_artist_ids", - "description": "

Spotify artist IDs.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "releases", - "description": "

Album release info.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "releases.cm_album", - "description": "

Chartmetric Album ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "releases.album_name", - "description": "

Album name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "releases.release_date", - "description": "

Album release date.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "releases.label", - "description": "

Album label.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "releases.image_url", - "description": "

Album image url.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "weekInsights", - "description": "

7-day growth insights.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "weekInsights.fdiff", - "description": "

Follower difference

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "weekInsights.fdiff_percent", - "description": "

Follower difference percentage

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "weekInsights.sns", - "description": "

Type of metric

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "weekInsights.value", - "description": "

Value

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "insights", - "description": "

30-day growth insights.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "insights.fdiff", - "description": "

Follower difference

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "insights.fdiff_percent", - "description": "

Follower difference percentage

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "insights.sns", - "description": "

Type of metric

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "insights.value", - "description": "

Value

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "twoMonthInsights", - "description": "

60-day growth insights.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "twoMonthInsights.fdiff", - "description": "

Follower difference

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "twoMonthInsights.fdiff_percent", - "description": "

Follower difference percentage

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "twoMonthInsights.sns", - "description": "

Type of metric

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "towMonthInsights.value", - "description": "

Value

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_followers", - "description": "

Number of Spotify followers.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response (spotify_monthly_listeners):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"name\": \"jnhygs\",\n \"code2\": null,\n \"image_url\": \"https://i.scdn.co/image/ab67616100005174e370de7eb3169eeeb57bf5d1\",\n \"tags\": [\n \"Hip-Hop/Rap\",\n \"Pop\",\n \"Electronic\"\n ],\n \"spotify_artist_ids\": [\n \"0H942IkjXv9bjx5OxoG7kh\"\n ],\n \"id\": \"9995920\",\n \"releases\": [\n {\n \"cm_album\": 26281529,\n \"album_name\": \"XTAYALIVE\",\n \"release_date\": \"2023-02-19T00:00:00.000Z\",\n \"label\": \"4990469 Records DK\",\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b2734b1357973723dcf2e1a9cbe0\",\n \"upc\": \"197631029842\"\n },\n {\n \"cm_album\": 26693485,\n \"album_name\": \"goin up!\",\n \"release_date\": \"2023-03-31T00:00:00.000Z\",\n \"label\": \"5150767 Records DK\",\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b2734344a9ce843717281f19871d\",\n \"upc\": \"197727921890\"\n }\n ],\n \"weekInsights\": {\n \"fdiff\": 388076,\n \"fdiff_percent\": 21.7568,\n \"sns\": \"spotify_m\",\n \"value\": 2171776\n },\n \"insights\": {\n \"fdiff\": 1896287,\n \"fdiff_percent\": 688.3349,\n \"sns\": \"spotify_m\",\n \"value\": 2171776\n },\n \"twoMonthInsights\": {\n \"fdiff\": null,\n \"fdiff_percent\": null,\n \"sns\": \"spotify_m\",\n \"value\": 2171776\n },\n \"sp_followers\": 13371,\n \"signed\": false\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (instagram_followers):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"name\": \"vinnie\",\n \"code2\": null,\n \"image_url\": \"https://i.scdn.co/image/ab676161000051746d9f84e5d0aeb4c4d8014db4\",\n \"tags\": [\n \"Hip-Hop/Rap\",\n \"Pop\",\n \"Alternative\"\n ],\n \"spotify_artist_ids\": [\n \"49gybyXyHSReIroJ8CpriJ\"\n ],\n \"id\": \"4161424\",\n \"releases\": [\n {\n \"cm_album\": 7253848,\n \"album_name\": \"cavity\",\n \"release_date\": \"2020-12-30T00:00:00.000Z\",\n \"label\": \"2660918 Records DK2\",\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b2738ef49fbe363673ab59d00e11\",\n \"upc\": \"195918408076\"\n },\n {\n \"cm_album\": 26316259,\n \"album_name\": \"Free - Single\",\n \"release_date\": \"2023-02-23T00:00:00.000Z\",\n \"label\": \"2660918 Records DK2\",\n \"image_url\": \"https://is3-ssl.mzstatic.com/image/thumb/Music116/v4/cc/bd/43/ccbd437d-fceb-16db-2713-0af56fc68e27/artwork.jpg/160x160bb.jpg\",\n \"upc\": \"197629824626\"\n }\n ],\n \"weekInsights\": {\n \"fdiff\": 0,\n \"fdiff_percent\": 0,\n \"sns\": \"instagram_f\",\n \"value\": 5660740\n },\n \"insights\": {\n \"fdiff\": -44022,\n \"fdiff_percent\": -0.7717,\n \"sns\": \"instagram_f\",\n \"value\": 5660740\n },\n \"twoMonthInsights\": {\n \"fdiff\": -39366,\n \"fdiff_percent\": -0.6906,\n \"sns\": \"instagram_f\",\n \"value\": 5660740\n },\n \"sp_followers\": 93720,\n \"signed\": false\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage (spotify_monthly_listeners):", - "content": "curl -H 'Authorization: Bearer ACCESS_KEY' https://api.chartmetric.com/api/artist/anr/by/social-index?sortBy=spotify_monthly_listeners&limit=15&offset=1&recentReleaseWithin=1080&latestReleaseWithin=540&maxSpotifyFollowers=20000", - "type": "curl" - }, - { - "title": "Example usage (instagram_followers):", - "content": "curl -H 'Authorization: Bearer ACCESS_KEY' https://api.chartmetric.com/api/artist/anr/by/social-index?sortBy=instagram_followers&limit=5&offset=1&sortOrderDesc=false", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/albums", - "title": "Albums", - "version": "1.0.0", - "name": "GetArtistAlbums", - "description": "

Get the list of albums featuring this artist.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Number", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "release_date", - "spotify_popularity" - ], - "optional": true, - "field": "sortColumn", - "defaultValue": "release_date", - "description": "

Sort by column.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "sortOrderDesc", - "defaultValue": "true", - "description": "

Whether to sort in descending order.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

the offset of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

The number of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "isPrimary", - "defaultValue": "false", - "description": "

Whether to return only primary albums. Primary album means the album where the artist is one of the main artists.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric's Album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "upc", - "description": "

Album UPC

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Album's name

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "release_date", - "description": "

Album's release date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "label", - "description": "

Album's Label

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "spotify_popularity", - "description": "

Number of Spotify popularity.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_label", - "description": "

Chartmetric label.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "description", - "description": "

Album description.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "created_at", - "description": "

Album created date.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "modified_at", - "description": "

Album modified date.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_album", - "description": "

Chartmetric Album Id.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "num_track", - "description": "

Number of track.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_album_ids", - "description": "

Spotify album ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "itunes_album_ids", - "description": "

Itunes album ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "deezer_album_ids", - "description": "

Deezer album ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artists", - "description": "

Chartmetric Artist info

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.cm_artist", - "description": "

Chartmetric Artist id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Chartmetric Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.image_url", - "description": "

Chartmetric Artist image

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 370590,\n \"upc\": \"886444975025\",\n \"name\": \"BEYONCÉ [Platinum Edition]\",\n \"image_url\": \"https://i.scdn.co/image/fd3cac76a8f1f3cab516f06873eeec16977efdf0\",\n \"release_date\": \"2014-11-24T00:00:00.000Z\",\n \"label\": \"Parkwood Entertainment/Columbia\",\n \"spotify_popularity\": 77,\n \"cm_label\": 41089,\n \"description\": null,\n \"created_at\": null,\n \"modified_at\": \"2020-06-02T00:00:28.227Z\",\n \"cm_album\": 370590,\n \"num_track\": \"20\",\n \"spotify_album_ids\": [\n \"2UJwKSBUz6rtW4QLK74kQu\"\n ],\n \"itunes_album_ids\": [\n \"1308530355\",\n \"939780552\",\n \"939775882\",\n \"939779719\"\n ],\n \"deezer_album_ids\": [\n \"9163719\"\n ],\n \"artists\": [\n {\n \"cm_artist\": 2838,\n \"name\": \"Beyoncé\",\n \"image_url\": \"https://i.scdn.co/image/ad8b0e5a18a5a443a2678768bd73f59833941abc\"\n }\n ]\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/3380/albums", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/cpp", - "title": "CPP (Cross-Platform Performance)", - "version": "1.0.0", - "name": "GetArtistCPP", - "description": "

Get the CPP rank and score for this artist.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "rank", - "score" - ], - "optional": false, - "field": "stat", - "description": "

CPP Statistic

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "since", - "description": "

start of the date range, in ISO date format (e.g. "2017-03-25"). This parameter is required

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

end of the date range, in ISO date format (e.g. "2017-03-25").

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date. If this is true, since/until parameters will be ignored

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "score/rank", - "description": "

Requested statistic. rank is Chartmetric Artist Rank. score is the final calculated score out of 1, rank is based on the ranking of this score.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "timestp", - "description": "

The date at which the requested statistic was achieved

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"score\": 0.777289463353648,\n \"timestp\": \"2019-12-10T00:00:00.000Z\"\n },\n {\n \"score\": 0.812443293506014,\n \"timestp\": \"2019-12-12T00:00:00.000Z\"\n },\n {\n \"score\": 0.758845785647956,\n \"timestp\": \"2019-12-13T00:00:00.000Z\"\n },\n {\n \"score\": 0.793494709937929,\n \"timestp\": \"2019-12-14T00:00:00.000Z\"\n },\n {\n \"score\": 0.805720732987306,\n \"timestp\": \"2019-12-15T00:00:00.000Z\"\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/3380/cpp?stat=score&since=2019-12-10&until=2019-12-15", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/career", - "title": "Career History", - "version": "1.0.0", - "name": "GetArtistCareer", - "description": "

Get Artist’s historical career information. The structure parallels that of career_status (see Get Artists (with filters) or Metadata) with the addition of a timestp field.
"Career Trend" data has been renamed to "Recent Momentum" with a revised algorithm after 05-12-23.
Data before May 12, 2023 is only available with legacy=true.
Data after Sep 20, 2023 is only available with legacy=false.
"Career Trend" data with legacy=true will not be supported after Oct 18, 2023.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "since", - "description": "

start of the date range, in ISO date format (e.g. "2023-05-12")

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

end of the date range, in ISO date format (e.g. "2023-07-29")

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "legacy", - "defaultValue": "false", - "description": "

The version of the career data. This parameter will be removed after Oct 18, 2023.

" - } - ] - } - }, - "success": { - "fields": { - "legacy = false": [ - { - "group": "legacy = false", - "type": "Date", - "optional": false, - "field": "timestp", - "description": "

The date at which the requested statistic was achieved.

" - }, - { - "group": "legacy = false", - "type": "String", - "optional": false, - "field": "stage", - "description": "

Artist's career stage was shown on the dashboard.

Possible values in order: undiscovered, developing, mid-level, mainstream, superstar, legendary

" - }, - { - "group": "legacy = false", - "type": "Number", - "optional": false, - "field": "stage_score", - "description": "

Value from 0 to 100 representing Artist's relative progression through their current stage.

For example, a score of 100 in the undiscovered stage is directly adjacent to a score of 0 in the developing stage.

" - }, - { - "group": "legacy = false", - "type": "String", - "optional": false, - "field": "momentum", - "description": "

Artist's recent momentum category as was shown on the dashboard.

Possible values in order: decline, gradual decline, steady, growth, explosive growth.

" - }, - { - "group": "legacy = false", - "type": "Number", - "optional": false, - "field": "momentum_score", - "description": "

Value from 0 to 100 representing the relative strength of the Artist's momentum within their momentum category.

For example, a score of 100 in the steady category is directly adjacent to a score of 0 in the growth category.

" - } - ], - "legacy = true": [ - { - "group": "legacy = true", - "type": "Date", - "optional": false, - "field": "timestp", - "description": "

The date at which the requested statistic was achieved.

" - }, - { - "group": "legacy = true", - "type": "String", - "optional": false, - "field": "stage", - "description": "

Artist's career stage was shown on the dashboard.

Possible values in order: undiscovered, developing, mid-level, mainstream, superstar, legendary

" - }, - { - "group": "legacy = true", - "type": "Number", - "optional": false, - "field": "stage_score", - "description": "

Value from 0 to 100 representing Artist's relative progression through their current stage.

For example, a score of 100 in the undiscovered stage is directly adjacent to a score of 0 in the developing stage.

" - }, - { - "group": "legacy = true", - "type": "String", - "optional": false, - "field": "trend", - "description": "

Artist's career trend category as was shown on the dashboard.

Possible values in order: strong decline, decline, even, growth, high growth, explosive growth.

" - }, - { - "group": "legacy = true", - "type": "Number", - "optional": false, - "field": "trend_score", - "description": "

Value from 0 to 100 representing the relative strength of the Artist's trend within their trend category.

For example, a score of 100 in the even category is directly adjacent to a score of 0 in the growth category.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response (legacy = false):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"timestp\": \"2023-07-26\",\n \"stage\": \"superstar\",\n \"stage_score\": 68,\n \"momentum\": \"decline\",\n \"momentum_score\": 100\n },\n {\n \"timestp\": \"2023-07-23\",\n \"stage\": \"superstar\",\n \"stage_score\": 68,\n \"momentum\": \"decline\",\n \"momentum_score\": 100\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (legacy = true):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"timestp\": \"2022-11-30\",\n \"stage\": \"superstar\",\n \"stage_score\": 76,\n \"trend\": \"even\",\n \"trend_score\": 52\n },\n {\n \"timestp\": \"2022-11-27\",\n \"stage\": \"superstar\",\n \"stage_score\": 76,\n \"trend\": \"even\",\n \"trend_score\": 55\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage (legacy = false):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/206557/career?limit=5&offset=5", - "type": "curl" - }, - { - "title": "Example usage (legacy = true):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/206557/career?limit=5&offset=5&legacy=true&since=2022-10-01&until=2022-12-20", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/cmStats", - "title": "Artist's Cached Stats & Trends", - "version": "1.0.0", - "name": "GetArtistCmStats", - "group": "Artist", - "description": "

Retrieve the cached social and streaming statistics of a given artist, including platform-specific fan base and engagement indicators, as well as overall Chartmetric scores and percentile ranks.

This endpoint provides an aggregated snapshot of the artist’s performance across major music and social platforms (Spotify, YouTube, TikTok, Instagram, etc.), including metrics such as followers, listeners, playlist reach, video views, and engagement ranks - all normalized within Chartmetric’s ranking system.

Response: Platform Metric (Scrollable)

Field Type Description
sp_followersInteger | FloatSpotify followers
deezer_fansInteger | FloatDeezer fans
facebook_followersInteger | FloatFacebook followers
twitter_followersInteger | FloatTwitter followers
soundcloud_followersInteger | FloatSoundCloud followers
sp_popularityInteger | FloatSpotify popularity score
sp_monthly_listenersInteger | FloatSpotify monthly listeners
ins_followersInteger | FloatInstagram followers
ycs_subscribersInteger | FloatYouTube subscribers
ycs_viewsInteger | FloatYouTube views
tiktok_followersInteger | FloatTikTok followers
tiktok_likesInteger | FloatTikTok likes
tiktok_top_video_viewsInteger | FloatTop TikTok video views
tiktok_top_video_commentsInteger | FloatTop TikTok video comments
tiktok_track_postsInteger | FloatTikTok track posts
line_music_artist_likesInteger | FloatLine Music artist likes
line_music_likesInteger | FloatLine Music likes
line_music_playsInteger | FloatLine Music plays
line_music_mv_playsInteger | FloatLine Music MV plays
melon_artist_fansInteger | FloatMelon artist fans
melon_likesInteger | FloatMelon likes
melon_video_likesInteger | FloatMelon video likes
melon_video_viewsInteger | FloatMelon video views
youtube_daily_video_viewsInteger | FloatYouTube daily video views
youtube_monthly_video_viewsInteger | FloatYouTube monthly video views
twitch_followersInteger | FloatTwitch followers
twitch_viewsInteger | FloatTwitch views
twitch_monthly_viewer_hoursInteger | FloatTwitch monthly viewer hours
twitch_weekly_viewer_hoursInteger | FloatTwitch weekly viewer hours
pandora_listeners_28_dayInteger | FloatPandora listeners (28-day)
pandora_lifetime_stations_addedInteger | FloatPandora stations added (lifetime)
pandora_lifetime_streamsInteger | FloatPandora streams (lifetime)
boomplay_ranking_currentInteger | FloatBoomplay current ranking
boomplay_favoritesInteger | FloatBoomplay favorites
boomplay_sharesInteger | FloatBoomplay shares
boomplay_commentsInteger | FloatBoomplay comments
boomplay_playsInteger | FloatBoomplay plays
num_sp_editorial_playlistsInteger | FloatNumber of Spotify editorial playlists
num_sp_playlistsInteger | FloatNumber of Spotify playlists
sp_playlist_total_reachInteger | FloatSpotify playlist total reach
sp_editorial_playlist_total_reachInteger | FloatSpotify editorial playlist total reach
num_am_editorial_playlistsInteger | FloatNumber of Apple Music editorial playlists
num_am_playlistsInteger | FloatNumber of Apple Music playlists
num_de_editorial_playlistsInteger | FloatNumber of Deezer editorial playlists
num_de_playlistsInteger | FloatNumber of Deezer playlists
de_playlist_total_reachInteger | FloatDeezer playlist total reach
de_editorial_playlist_total_reachInteger | FloatDeezer editorial playlist total reach
num_az_editorial_playlistsInteger | FloatNumber of Amazon Music editorial playlists
num_az_playlistsInteger | FloatNumber of Amazon Music playlists
num_yt_editorial_playlistsInteger | FloatNumber of YouTube editorial playlists
num_yt_playlistsInteger | FloatNumber of YouTube playlists
yt_playlist_total_reachInteger | FloatYouTube playlist total reach
yt_editorial_playlist_total_reachInteger | FloatYouTube editorial playlist total reach
shazam_countInteger | FloatShazam count
genius_pageviewsInteger | FloatGenius pageviews
radio_spinsInteger | FloatRadio spins
siriusXm_spinsInteger | FloatSiriusXM spins
songkick_fansInteger | FloatSongkick fans
cm_artist_rankInteger | FloatGlobal artist rank
cm_artist_rank_percentileInteger | FloatArtist rank percentile
fan_base_rankInteger | FloatFan base rank
fan_base_rank_percentileInteger | FloatFan base rank percentile
engagement_rankInteger | FloatEngagement rank
engagement_rank_percentileInteger | FloatEngagement rank percentile
artist_scoreInteger | FloatChartmetric artist score

Response: Rank (Scrollable)

Field Type Description
sp_popularity_rankIntegerSpotify popularity rank
deezer_fans_rankIntegerDeezer fans rank
sp_followers_rankIntegerSpotify followers rank
sp_monthly_listeners_rankIntegerSpotify monthly listeners rank
ins_followers_rankIntegerInstagram followers rank
ycs_subscribers_rankIntegerYouTube subscribers rank
ycs_views_rankIntegerYouTube views rank
tiktok_followers_rankIntegerTikTok followers rank
tiktok_track_posts_rankIntegerTikTok track posts rank
tiktok_likes_rankIntegerTikTok likes rank
tiktok_top_video_views_rankIntegerTikTok top video views rank
line_music_artist_likes_rankIntegerLine Music artist likes rank
line_music_likes_rankIntegerLine Music likes rank
line_music_plays_rankIntegerLine Music plays rank
line_music_mv_plays_rankIntegerLine Music MV plays rank
melon_artist_fans_rankIntegerMelon artist fans rank
melon_likes_rankIntegerMelon likes rank
melon_video_likes_rankIntegerMelon video likes rank
melon_video_views_rankIntegerMelon video views rank
youtube_daily_video_views_rankIntegerYouTube daily video views rank
youtube_monthly_video_views_rankIntegerYouTube monthly video views rank
twitch_followers_rankIntegerTwitch followers rank
twitch_views_rankIntegerTwitch views rank
twitch_monthly_viewer_hours_rankIntegerTwitch monthly viewer hours rank
twitch_weekly_viewer_hours_rankIntegerTwitch weekly viewer hours rank
pandora_listeners_28_day_rankIntegerPandora listeners (28-day) rank
pandora_lifetime_stations_added_rankIntegerPandora stations added (lifetime) rank
pandora_lifetime_streams_rankIntegerPandora streams (lifetime) rank
boomplay_ranking_current_rankIntegerBoomplay current ranking rank
boomplay_favorites_rankIntegerBoomplay favorites rank
boomplay_shares_rankIntegerBoomplay shares rank
boomplay_comments_rankIntegerBoomplay comments rank
boomplay_plays_rankIntegerBoomplay plays rank
radio_spins_rankIntegerRadio spins rank
siriusXm_spins_rankIntegerSiriusXM spins rank
", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric Artist ID

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "timestamp", - "description": "

Snapshot timestamp

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "latest", - "description": "

Latest Platform Metrics + Platform Metric Ranks

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "weekly_diff", - "description": "

Weekly differences of Platform Metrics

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "weekly_diff_percent", - "description": "

Weekly differences of Platform Metrics in percent

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "monthly_diff", - "description": "

Monthly differences of Platform Metrics

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "monthly_diff_percent", - "description": "

Monthly differences of Platform Metrics in percent

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "bimonthly_diff", - "description": "

Bimonthly differences of Platform Metrics

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "bimonthly_diff_percent", - "description": "

Bimonthly differences of Platform Metrics in percent

" - } - ] - }, - "examples": [ - { - "title": "Example usage", - "content": "GET curl -H 'Authorization: Bearer ACCESS_KEY' https://api.chartmetric.com/api/artist/3986/cmStats\n\n{\n \"obj\": {\n \"timestamp\": \"2025-10-16T09:39:26Z\",\n \"latest\": {\n \"sp_followers\": 51385645,\n \"deezer_fans\": 5500765,\n \"facebook_followers\": 15000000,\n \"soundcloud_followers\": 8491362,\n \"sp_popularity\": 88,\n \"sp_monthly_listeners\": 61398738,\n \"ins_followers\": 22894076,\n \"ycs_subscribers\": 17900000,\n \"ycs_views\": 10412350783,\n \"tiktok_followers\": 6200000,\n \"tiktok_likes\": 3100000,\n \"tiktok_top_video_views\": 28325708904,\n \"tiktok_top_video_comments\": 30873430,\n \"tiktok_track_posts\": 10458886,\n \"line_music_artist_likes\": 5999,\n \"line_music_likes\": 4313,\n \"line_music_plays\": 690564,\n \"melon_artist_fans\": 26503,\n \"melon_likes\": 76043,\n \"melon_video_likes\": 205,\n \"melon_video_views\": 12329,\n \"youtube_daily_video_views\": 7156421,\n \"youtube_monthly_video_views\": 197031758,\n \"twitch_followers\": 33,\n \"pandora_listeners_28_day\": 1172694,\n \"pandora_lifetime_stations_added\": 12538819,\n \"pandora_lifetime_streams\": 3264725013,\n \"boomplay_ranking_current\": 1335,\n \"boomplay_favorites\": 651,\n \"boomplay_shares\": 28,\n \"boomplay_comments\": 133,\n \"boomplay_plays\": 13300000,\n \"shazam_count\": 121577487,\n \"genius_pageviews\": 75554729,\n \"songkick_fans\": 4800943,\n \"cm_artist_rank\": 40,\n \"cm_artist_rank_percentile\": 1,\n \"fan_base_rank\": 53,\n \"fan_base_rank_percentile\": 1,\n \"engagement_rank\": 39,\n \"engagement_rank_percentile\": 1,\n \"artist_score\": 97.58563625103979,\n \"sp_popularity_rank\": 22,\n \"deezer_fans_rank\": 41,\n \"sp_followers_rank\": 24,\n \"sp_monthly_listeners_rank\": 28,\n \"ins_followers_rank\": 243,\n \"ycs_subscribers_rank\": 160,\n \"ycs_views_rank\": 156,\n \"tiktok_followers_rank\": 794,\n \"tiktok_track_posts_rank\": 321,\n \"tiktok_likes_rank\": 12419,\n \"tiktok_top_video_views_rank\": 75,\n \"line_music_artist_likes_rank\": 1296,\n \"line_music_likes_rank\": 1726,\n \"line_music_plays_rank\": 1649,\n \"line_music_mv_plays_rank\": null,\n \"melon_artist_fans_rank\": 432,\n \"melon_likes_rank\": 1063,\n \"melon_video_likes_rank\": 1420,\n \"melon_video_views_rank\": 1575,\n \"youtube_daily_video_views_rank\": 71,\n \"youtube_monthly_video_views_rank\": 81,\n \"twitch_followers_rank\": 6057,\n \"twitch_views_rank\": null,\n \"twitch_monthly_viewer_hours_rank\": null,\n \"twitch_weekly_viewer_hours_rank\": null,\n \"pandora_listeners_28_day_rank\": 639,\n \"pandora_lifetime_stations_added_rank\": 112,\n \"pandora_lifetime_streams_rank\": 230,\n \"boomplay_ranking_current_rank\": 62401,\n \"boomplay_favorites_rank\": 555,\n \"boomplay_shares_rank\": 869,\n \"boomplay_comments_rank\": 232,\n \"boomplay_plays_rank\": 449,\n \"radio_spins_rank\": 647,\n \"siriusXm_spins_rank\": 771\n },\n \"weekly_diff\": {\n \"sp_followers\": 164715,\n \"deezer_fans\": 8033,\n \"facebook_followers\": 0,\n \"twitter_followers\": 308,\n \"soundcloud_followers\": 2074,\n \"sp_popularity\": -1,\n \"sp_monthly_listeners\": 814320,\n \"ins_followers\": 80124,\n \"ycs_subscribers\": 0,\n \"ycs_views\": 0,\n \"tiktok_followers\": 0,\n \"tiktok_likes\": 0,\n \"tiktok_top_video_views\": 28957917,\n \"tiktok_top_video_comments\": -10432,\n \"tiktok_track_posts\": 40998,\n \"line_music_artist_likes\": 16,\n \"line_music_likes\": 11,\n \"line_music_plays\": 2187,\n \"line_music_mv_plays\": null,\n \"melon_artist_fans\": 10,\n \"melon_likes\": 20177,\n \"melon_video_likes\": 0,\n \"melon_video_views\": 4,\n \"youtube_daily_video_views\": -11617269,\n \"youtube_monthly_video_views\": -1131382,\n \"twitch_followers\": 0,\n \"twitch_views\": null,\n \"twitch_monthly_viewer_hours\": null,\n \"twitch_weekly_viewer_hours\": null,\n \"pandora_listeners_28_day\": 15856,\n \"pandora_lifetime_stations_added\": 15741,\n \"pandora_lifetime_streams\": 767662,\n \"boomplay_ranking_current\": 476,\n \"boomplay_favorites\": 0,\n \"boomplay_shares\": 0,\n \"boomplay_comments\": 0,\n \"boomplay_plays\": 100000,\n \"num_sp_editorial_playlists\": 2,\n \"num_sp_playlists\": 6411,\n \"sp_playlist_total_reach\": -126966,\n \"sp_editorial_playlist_total_reach\": -664182,\n \"num_am_editorial_playlists\": -1,\n \"num_am_playlists\": -2,\n \"num_de_editorial_playlists\": 0,\n \"num_de_playlists\": -7,\n \"de_playlist_total_reach\": -577495,\n \"de_editorial_playlist_total_reach\": 3838,\n \"num_az_editorial_playlists\": 8,\n \"num_az_playlists\": 8,\n \"num_yt_editorial_playlists\": 0,\n \"num_yt_playlists\": 0,\n \"yt_playlist_total_reach\": 7620,\n \"yt_editorial_playlist_total_reach\": 0,\n \"shazam_count\": 263651,\n \"genius_pageviews\": 63143,\n \"radio_spins\": 1439,\n \"siriusXm_spins\": 23,\n \"songkick_fans\": 5023,\n \"cm_artist_rank\": 0,\n \"cm_artist_rank_percentile\": null,\n \"fan_base_rank\": -5,\n \"fan_base_rank_percentile\": null,\n \"engagement_rank\": 2,\n \"engagement_rank_percentile\": null,\n \"artist_score\": 0.0003623986535785662\n },\n \"weekly_diff_percent\": {\n \"sp_followers\": 0.3216,\n \"deezer_fans\": 0.1462,\n \"facebook_followers\": 0,\n \"twitter_followers\": 0.0928,\n \"soundcloud_followers\": 0.0244,\n \"sp_popularity\": -1.1236,\n \"sp_monthly_listeners\": 1.3441,\n \"ins_followers\": 0.3512,\n \"ycs_subscribers\": 0,\n \"ycs_views\": 0,\n \"tiktok_followers\": 0,\n \"tiktok_likes\": 0,\n \"tiktok_top_video_views\": 0.1023,\n \"tiktok_top_video_comments\": -0.0338,\n \"tiktok_track_posts\": 0.3935,\n \"line_music_artist_likes\": 0.2674,\n \"line_music_likes\": 0.2557,\n \"line_music_plays\": 0.3177,\n \"line_music_mv_plays\": null,\n \"melon_artist_fans\": 0.0377,\n \"melon_likes\": 36.1168,\n \"melon_video_likes\": 0,\n \"melon_video_views\": 0.0325,\n \"youtube_daily_video_views\": -24.6176,\n \"youtube_monthly_video_views\": -0.5709,\n \"twitch_followers\": 0,\n \"twitch_views\": null,\n \"twitch_monthly_viewer_hours\": null,\n \"twitch_weekly_viewer_hours\": null,\n \"pandora_listeners_28_day\": 1.3706,\n \"pandora_lifetime_stations_added\": 0.1257,\n \"pandora_lifetime_streams\": 0.0235,\n \"boomplay_ranking_current\": 55.4133,\n \"boomplay_favorites\": 0,\n \"boomplay_shares\": 0,\n \"boomplay_comments\": 0,\n \"boomplay_plays\": 0.7576,\n \"num_sp_editorial_playlists\": 0.2554,\n \"num_sp_playlists\": 0.4131,\n \"sp_playlist_total_reach\": -0.0257,\n \"sp_editorial_playlist_total_reach\": -0.9823,\n \"num_am_editorial_playlists\": -0.1475,\n \"num_am_playlists\": -0.1198,\n \"num_de_editorial_playlists\": 0,\n \"num_de_playlists\": -0.9845,\n \"de_playlist_total_reach\": -5.8529,\n \"de_editorial_playlist_total_reach\": 0.0514,\n \"num_az_editorial_playlists\": 1.3559,\n \"num_az_playlists\": 1.3029,\n \"num_yt_editorial_playlists\": 0,\n \"num_yt_playlists\": 0,\n \"yt_playlist_total_reach\": 0.0003,\n \"yt_editorial_playlist_total_reach\": 0,\n \"shazam_count\": 0.2173,\n \"genius_pageviews\": 0.0836,\n \"radio_spins\": 0.3596,\n \"siriusXm_spins\": 0.1774,\n \"songkick_fans\": 0.1047,\n \"cm_artist_rank\": 0,\n \"cm_artist_rank_percentile\": null,\n \"fan_base_rank\": -8.620700000000001,\n \"fan_base_rank_percentile\": null,\n \"engagement_rank\": 5.4054,\n \"engagement_rank_percentile\": null,\n \"artist_score\": 0.0003713661285889855\n },\n \"monthly_diff\": {\n \"sp_followers\": 722941,\n \"deezer_fans\": 31896,\n \"facebook_followers\": 0,\n \"twitter_followers\": 1062,\n \"soundcloud_followers\": 9026,\n \"sp_popularity\": -4,\n \"sp_monthly_listeners\": 2472654,\n \"ins_followers\": 250227,\n \"ycs_subscribers\": 100000,\n \"ycs_views\": 103781092,\n \"tiktok_followers\": 0,\n \"tiktok_likes\": 100000,\n \"tiktok_top_video_views\": 84604228,\n \"tiktok_top_video_comments\": 5459,\n \"tiktok_track_posts\": 229994,\n \"line_music_artist_likes\": 48,\n \"line_music_likes\": 48,\n \"line_music_plays\": 8483,\n \"line_music_mv_plays\": null,\n \"melon_artist_fans\": 40,\n \"melon_likes\": 18135,\n \"melon_video_likes\": 0,\n \"melon_video_views\": 9,\n \"youtube_daily_video_views\": -20368552,\n \"youtube_monthly_video_views\": -5389137,\n \"twitch_followers\": 0,\n \"twitch_views\": null,\n \"twitch_monthly_viewer_hours\": null,\n \"twitch_weekly_viewer_hours\": null,\n \"pandora_listeners_28_day\": 2399,\n \"pandora_lifetime_stations_added\": 21510,\n \"pandora_lifetime_streams\": 5104683,\n \"boomplay_ranking_current\": 330,\n \"boomplay_favorites\": 3,\n \"boomplay_shares\": 0,\n \"boomplay_comments\": 0,\n \"boomplay_plays\": 500000,\n \"num_sp_editorial_playlists\": -3,\n \"num_sp_playlists\": 19655,\n \"sp_playlist_total_reach\": -3769740,\n \"sp_editorial_playlist_total_reach\": -1073890,\n \"num_am_editorial_playlists\": -3,\n \"num_am_playlists\": 1,\n \"num_de_editorial_playlists\": -1,\n \"num_de_playlists\": -4,\n \"de_playlist_total_reach\": -555751,\n \"de_editorial_playlist_total_reach\": -97445,\n \"num_az_editorial_playlists\": 38,\n \"num_az_playlists\": 39,\n \"num_yt_editorial_playlists\": -1,\n \"num_yt_playlists\": -4,\n \"yt_playlist_total_reach\": -14298824,\n \"yt_editorial_playlist_total_reach\": 0,\n \"shazam_count\": 1222924,\n \"genius_pageviews\": 344978,\n \"radio_spins\": 5580,\n \"siriusXm_spins\": 92,\n \"songkick_fans\": 20381,\n \"cm_artist_rank\": -1,\n \"cm_artist_rank_percentile\": null,\n \"fan_base_rank\": -5,\n \"fan_base_rank_percentile\": null,\n \"engagement_rank\": -4,\n \"engagement_rank_percentile\": null,\n \"artist_score\": 0.01646842125245485\n },\n \"monthly_diff_percent\": {\n \"sp_followers\": 1.427,\n \"deezer_fans\": 0.5832,\n \"facebook_followers\": 0,\n \"twitter_followers\": 0.3208,\n \"soundcloud_followers\": 0.1064,\n \"sp_popularity\": -4.3478,\n \"sp_monthly_listeners\": 4.1962,\n \"ins_followers\": 1.1059,\n \"ycs_subscribers\": 0.5618,\n \"ycs_views\": 1.0067,\n \"tiktok_followers\": 0,\n \"tiktok_likes\": 3.3333,\n \"tiktok_top_video_views\": 0.2996,\n \"tiktok_top_video_comments\": 0.0177,\n \"tiktok_track_posts\": 2.2485,\n \"line_music_artist_likes\": 0.8066,\n \"line_music_likes\": 1.1254,\n \"line_music_plays\": 1.2437,\n \"line_music_mv_plays\": null,\n \"melon_artist_fans\": 0.1512,\n \"melon_likes\": 31.3169,\n \"melon_video_likes\": 0,\n \"melon_video_views\": 0.0731,\n \"youtube_daily_video_views\": -10.0528,\n \"youtube_monthly_video_views\": -2.6598,\n \"twitch_followers\": 0,\n \"twitch_views\": null,\n \"twitch_monthly_viewer_hours\": null,\n \"twitch_weekly_viewer_hours\": null,\n \"pandora_listeners_28_day\": 0.205,\n \"pandora_lifetime_stations_added\": 0.1718,\n \"pandora_lifetime_streams\": 0.1566,\n \"boomplay_ranking_current\": 32.8358,\n \"boomplay_favorites\": 0.463,\n \"boomplay_shares\": 0,\n \"boomplay_comments\": 0,\n \"boomplay_plays\": 3.9063,\n \"num_sp_editorial_playlists\": -0.3807,\n \"num_sp_playlists\": 1.2773,\n \"sp_playlist_total_reach\": -0.7587,\n \"sp_editorial_playlist_total_reach\": -1.5786,\n \"num_am_editorial_playlists\": -0.4412,\n \"num_am_playlists\": 0.06,\n \"num_de_editorial_playlists\": -0.7634,\n \"num_de_playlists\": -0.565,\n \"de_playlist_total_reach\": -5.645,\n \"de_editorial_playlist_total_reach\": -1.2879,\n \"num_az_editorial_playlists\": 6.7857,\n \"num_az_playlists\": 6.6895,\n \"num_yt_editorial_playlists\": -2.6316,\n \"num_yt_playlists\": -1.1331,\n \"yt_playlist_total_reach\": -0.5813,\n \"yt_editorial_playlist_total_reach\": 0,\n \"shazam_count\": 1.0161,\n \"genius_pageviews\": 0.4587,\n \"radio_spins\": 1.409,\n \"siriusXm_spins\": 0.7133,\n \"songkick_fans\": 0.4263,\n \"cm_artist_rank\": -2.439,\n \"cm_artist_rank_percentile\": null,\n \"fan_base_rank\": -8.620700000000001,\n \"fan_base_rank_percentile\": null,\n \"engagement_rank\": -9.302299999999999,\n \"engagement_rank_percentile\": null,\n \"artist_score\": 0.01687871447380238\n },\n \"bimonthly_diff\": {\n \"sp_followers\": 1571767,\n \"deezer_fans\": 75796,\n \"facebook_followers\": null,\n \"twitter_followers\": 4102,\n \"soundcloud_followers\": 21573,\n \"sp_popularity\": -3,\n \"sp_monthly_listeners\": 1621910,\n \"ins_followers\": 460078,\n \"ycs_subscribers\": 200000,\n \"ycs_views\": 311298492,\n \"tiktok_followers\": 100000,\n \"tiktok_likes\": 100000,\n \"tiktok_top_video_views\": 249325771,\n \"tiktok_top_video_comments\": 81885,\n \"tiktok_track_posts\": 801692,\n \"line_music_artist_likes\": 111,\n \"line_music_likes\": 112,\n \"line_music_plays\": 18741,\n \"line_music_mv_plays\": null,\n \"melon_artist_fans\": 36,\n \"melon_likes\": 1910,\n \"melon_video_likes\": -1,\n \"melon_video_views\": 18,\n \"youtube_daily_video_views\": -56653903,\n \"youtube_monthly_video_views\": -12926086,\n \"twitch_followers\": 0,\n \"twitch_views\": null,\n \"twitch_monthly_viewer_hours\": null,\n \"twitch_weekly_viewer_hours\": null,\n \"pandora_listeners_28_day\": -44571,\n \"pandora_lifetime_stations_added\": 20918,\n \"pandora_lifetime_streams\": -77875143,\n \"boomplay_ranking_current\": -232,\n \"boomplay_favorites\": 8,\n \"boomplay_shares\": 1,\n \"boomplay_comments\": 0,\n \"boomplay_plays\": null,\n \"num_sp_editorial_playlists\": -7,\n \"num_sp_playlists\": 37809,\n \"sp_playlist_total_reach\": -2395838,\n \"sp_editorial_playlist_total_reach\": -3869264,\n \"num_am_editorial_playlists\": -6,\n \"num_am_playlists\": 10,\n \"num_de_editorial_playlists\": -1,\n \"num_de_playlists\": -4,\n \"de_playlist_total_reach\": 516766,\n \"de_editorial_playlist_total_reach\": -47700,\n \"num_az_editorial_playlists\": 35,\n \"num_az_playlists\": 36,\n \"num_yt_editorial_playlists\": 31,\n \"num_yt_playlists\": 0,\n \"yt_playlist_total_reach\": -1711548,\n \"yt_editorial_playlist_total_reach\": 2884195,\n \"shazam_count\": 2926582,\n \"genius_pageviews\": 707631,\n \"radio_spins\": 12508,\n \"siriusXm_spins\": 195,\n \"songkick_fans\": 57986,\n \"cm_artist_rank\": -5,\n \"cm_artist_rank_percentile\": null,\n \"fan_base_rank\": -6,\n \"fan_base_rank_percentile\": null,\n \"engagement_rank\": -6,\n \"engagement_rank_percentile\": null,\n \"artist_score\": 0.1009151635119281\n },\n \"bimonthly_diff_percent\": {\n \"sp_followers\": 3.1657,\n \"deezer_fans\": 1.3987,\n \"facebook_followers\": null,\n \"twitter_followers\": 1.2513,\n \"soundcloud_followers\": 0.2548,\n \"sp_popularity\": -3.2609,\n \"sp_monthly_listeners\": 2.748,\n \"ins_followers\": 2.0582,\n \"ycs_subscribers\": 1.1299,\n \"ycs_views\": 3.0818,\n \"tiktok_followers\": 1.6393,\n \"tiktok_likes\": 3.3333,\n \"tiktok_top_video_views\": 0.8884,\n \"tiktok_top_video_comments\": 0.266,\n \"tiktok_track_posts\": 8.3274,\n \"line_music_artist_likes\": 1.8887,\n \"line_music_likes\": 2.6692,\n \"line_music_plays\": 2.7958,\n \"line_music_mv_plays\": null,\n \"melon_artist_fans\": 0.1361,\n \"melon_likes\": 2.5788,\n \"melon_video_likes\": -0.4854,\n \"melon_video_views\": 0.1462,\n \"youtube_daily_video_views\": -12.4015,\n \"youtube_monthly_video_views\": -6.1173,\n \"twitch_followers\": 0,\n \"twitch_views\": null,\n \"twitch_monthly_viewer_hours\": null,\n \"twitch_weekly_viewer_hours\": null,\n \"pandora_listeners_28_day\": -3.7104,\n \"pandora_lifetime_stations_added\": 0.1671,\n \"pandora_lifetime_streams\": -2.3305,\n \"boomplay_ranking_current\": -22.8346,\n \"boomplay_favorites\": 1.2442,\n \"boomplay_shares\": 3.7037,\n \"boomplay_comments\": 0,\n \"boomplay_plays\": null,\n \"num_sp_editorial_playlists\": -0.8861,\n \"num_sp_playlists\": 2.4945,\n \"sp_playlist_total_reach\": -0.4841,\n \"sp_editorial_playlist_total_reach\": -5.4756,\n \"num_am_editorial_playlists\": -0.8772,\n \"num_am_playlists\": 0.6024,\n \"num_de_editorial_playlists\": -0.7634,\n \"num_de_playlists\": -0.5642,\n \"de_playlist_total_reach\": 5.5736,\n \"de_editorial_playlist_total_reach\": -0.6349,\n \"num_az_editorial_playlists\": 6.2167,\n \"num_az_playlists\": 6.1433,\n \"num_yt_editorial_playlists\": 516.6667,\n \"num_yt_playlists\": 0,\n \"yt_playlist_total_reach\": -0.0699,\n \"yt_editorial_playlist_total_reach\": 3.6103,\n \"shazam_count\": 2.4701,\n \"genius_pageviews\": 0.9461,\n \"radio_spins\": 3.2226,\n \"siriusXm_spins\": 1.5256,\n \"songkick_fans\": 1.2237,\n \"cm_artist_rank\": -11.1111,\n \"cm_artist_rank_percentile\": null,\n \"fan_base_rank\": -10.1695,\n \"fan_base_rank_percentile\": null,\n \"engagement_rank\": -13.3333,\n \"engagement_rank_percentile\": null,\n \"artist_score\": 0.1035189539305551\n }\n }\n}", - "type": "curl" - } - ] - }, - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/:status/events", - "title": "Live Events", - "version": "1.0.0", - "name": "GetArtistEvents", - "description": "

Get Artist's live event information (concerts, festivals, performances) based on the status.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "\"current\"", - "\"past\"" - ], - "optional": false, - "field": "status", - "description": "

Status of the event, either "current" or "past". Note: "Songkick" events are only available for the "past" status.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "fromDaysAgo", - "description": "

Number of days ago to start the events from.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "toDaysAgo", - "description": "

Number of days ago to end the events from. Use a negative value to get future events.

" - }, - { - "group": "Parameter", - "type": "Integer", - "size": "0-100", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

Number of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

ID of the event.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "event_name", - "description": "

Name of the event.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "jambase_event_url", - "description": "

URL of the event on JamBase.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "songkick_event_url", - "description": "

URL of the event on Songkick.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "seatgeek_event_url", - "description": "

URL of the event on SeatGeek

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "ticketmaster_event_url", - "description": "

URL of the event on Ticketmaster.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "type", - "description": "

Type of the event.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "start_date", - "description": "

Start date of the event.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "end_date", - "description": "

End date of the event.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "venue_id", - "description": "

ID of the venue.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "venue_name", - "description": "

Name of the venue.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "venue_url", - "description": "

URL of the venue.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "venue_capacity", - "description": "

Capacity of the venue.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "city", - "description": "

City of the event.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "city.id", - "description": "

ID of the city.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "city.name", - "description": "

Name of the city.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "code2", - "description": "

Country code of the event.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "low_price", - "description": "

Lowest price of the event.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "high_price", - "description": "

Highest price of the event.

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "price_trend", - "description": "

Price trend of the event.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "price_trend.date", - "description": "

Date of the price trend.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "price_trend.price", - "description": "

Price of the event on that date.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "currency", - "description": "

Currency of the event.

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "is_headliner", - "description": "

Whether the artist is a headliner for the event.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response: past", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 169,\n \"event_name\": \"Taylor Swift at BC Place\",\n \"jambase_event_url\": \"https://www.jambase.com/show/taylor-swift-bc-place-20241208\",\n \"songkick_event_url\": \"https://www.songkick.com/concerts/41522431-taylor-swift-at-bc-place-stadium?utm_source=46722&utm_medium=partner\",\n \"seatgeek_event_url\": \"https://seatgeek.com/taylor-swift-tickets/vancouver-canada-bc-place-stadium-2024-12-06-7-pm/concert/6260020\",\n \"ticketmaster_event_url\": \"https://ticketmaster.evyy.net/c/252938/271177/4272?u=https%3A%2F%2Fwww.ticketmaster.ca%2Ftaylor-swift-the-eras-tour-vancouver-british-columbia-12-08-2024%2Fevent%2F11005F5E62450C6C\",\n \"type\": \"Concert\",\n \"start_date\": \"2024-12-08 19:00:00.000\",\n \"end_date\": \"2024-12-08 00:00:00.000\",\n \"venue_id\": 2002,\n \"venue_name\": \"BC Place\",\n \"venue_url\": \"https://www.jambase.com/venue/bc-place\",\n \"venue_capacity\": 54500,\n \"city\": {\n \"id\": 1541,\n \"name\": \"Vancouver\"\n },\n \"code2\": \"CA\",\n \"low_price\": 2042,\n \"high_price\": 114273,\n \"price\": 8321,\n \"price_trend\": [\n {\n \"date\": \"2024-01-15\",\n \"price\": 4483\n },\n {\n \"date\": \"2024-01-15\",\n \"price\": 4133\n },\n ...\n ],\n \"currency\": \"USD\",\n \"is_headliner\": true\n },\n ...,\n}", - "type": "json" - }, - { - "title": "Success-Response: current", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n [\n {\n \"id\": 123,\n \"event_name\": \"Taylor Swift at BC Place\",\n \"event_url\": \"https://www.jambase.com/show/taylor-swift-bc-place-20241206\",\n \"type\": \"Concert\",\n \"start_date\": \"2024-12-06 19:00:00.000\",\n \"end_date\": \"2024-12-06 00:00:00.000\",\n \"venue_name\": \"BC Place\",\n \"venue_url\": \"https://www.jambase.com/venue/bc-place\",\n \"venue_capacity\": 54500,\n \"city\": \"Vancouver\",\n \"code2\": \"CA\"\n },\n {\n \"id\": 147,\n \"event_name\": \"Taylor Swift at BC Place\",\n \"event_url\": \"https://www.jambase.com/show/taylor-swift-bc-place-20241207\",\n \"type\": \"Concert\",\n \"start_date\": \"2024-12-07 19:00:00.000\",\n \"end_date\": \"2024-12-07 00:00:00.000\",\n \"venue_name\": \"BC Place\",\n \"venue_url\": \"https://www.jambase.com/venue/bc-place\",\n \"venue_capacity\": 54500,\n \"city\": \"Vancouver\",\n \"code2\": \"CA\"\n },\n ...,\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage: past", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/2762/past/events?fromDaysAgo=365&toDaysAgo=0", - "type": "curl" - }, - { - "title": "Example usage: current", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/2762/current/events?fromDaysAgo=0&toDaysAgo=-30", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/instagram-audience-stats", - "title": "Instagram Audience Data", - "version": "1.2.0", - "name": "GetArtistInstagramAudienceStats", - "description": "

Get latest Instagram Audience Analysis and Stats for an artist.

Note: We check for new reports daily and update the corresponding audience data only when a new report is present in its API. Most audience reports are updated on a weekly basis, so we should have at least one update per month for this data. However, when using the date parameter, some dates will return empty response structures. An empty response means that the data didn't receive an update for that date.

To avoid unnecessary requets, you can get the dates with available data first from the endpoint /api/artist/:id/instagram-audience-stats/dates.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "defaultValue": "today", - "description": "

Historical date in ISO date format (e.g. 2020-09-20). Data may not be available for all historical dates. If no date is specified, this endpoint will retrieve data for the most recent available date. The timestp field of the response specifies the retrieved data date.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "geoOnly", - "defaultValue": "false", - "description": "

If set to true, limits the response to top_countries, top_cities, likers_top_contries, likers_top_cities, followers, avg_likes_per_post, engagement_rate, and timestp. The other response fields are omitted. This reduces payload size and reduces response delay.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "top_countries", - "description": "

Top countries of Artist.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "top_countries.name", - "description": "

Artist country name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "top_countries.code", - "description": "

Artist country code.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "top_countries.percent", - "description": "

Percentage of followers in the country.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "top_countries.followers", - "description": "

Number of Instagram followers in the country.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "top_cities", - "description": "

Top cities of Artist.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "top_cities.name", - "description": "

Artist city name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "top_cities.state", - "description": "

Artist state name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "top_cities.country", - "description": "

Artist country name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "top_cities.percent", - "description": "

Percentage of followers in the city.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "top_cities.followers", - "description": "

Number of Instagram followers in the city.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "likers_top_countries", - "description": "

Top countries of Likers.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "likers_top_countries.name", - "description": "

Country name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "likers_top_countries.code", - "description": "

Country code.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "likers_top_countries.percent", - "description": "

Percentage of likers in the country.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "likers_top_countries.likes", - "description": "

Number of Instagram likes in the country.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "likers_top_cities", - "description": "

Top cities of Likers.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "likers_top_cities.name", - "description": "

City name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "likers_top_cities.percent", - "description": "

Percentage of likers in the city.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "likers_top_cities.likes", - "description": "

Number of Instagram likers in the city.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "likers_top_cities.state", - "description": "

State name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "likers_top_cities.country", - "description": "

Likers' country name.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "audience_genders_per_age", - "description": "

Audience genders per age.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_genders_per_age.code", - "description": "

Age range of audience.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_genders_per_age.male", - "description": "

Male ratio for this age range.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_genders_per_age.female", - "description": "

Female ratio for this age range.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "audience_ethnicities", - "description": "

Audience ethnicity ratio.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_ethnicities.code", - "description": "

Audience ethnicity code.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_ethnicities.name", - "description": "

Audience ethnicity name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_ethnicities.weight", - "description": "

Ratio of ethnicity.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "audience_genders", - "description": "

Audience gender ratio.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_genders.code", - "description": "

Male or Female audience.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_genders.weight", - "description": "

Ratio of gender.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "audience_brand_affinities", - "description": "

Audience brand affinity ratio.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_brand_affinities.name", - "description": "

Name of brand.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_brand_affinities.weight", - "description": "

Ratio of brand.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "audience_brand_affinities.affinity", - "description": "

Audience brand affinity.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "audience_interests", - "description": "

Audience interests.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_interests.name", - "description": "

Name of the interest.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_interests.weight", - "description": "

Ratio of interest.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "audience_interests.affinity", - "description": "

Audience interest affinity.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "audience_likers_genders_per_age", - "description": "

Likers Gender.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_likers_genders_per_age.code", - "description": "

Age range of likers.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_likers_genders_per_age.male", - "description": "

Male ratio for this age range.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_likers_genders_per_age.female", - "description": "

Female ratio for this age range.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "audience_likers_ethnicities", - "description": "

Likers ethnicity ratio.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_likers_ethnicities.code", - "description": "

Likers ethnicity code.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_likers_ethnicities.name", - "description": "

Likers ethnicity name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_likers_ethnicities.weight", - "description": "

Ratio of ethnicity.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "audience_likers_genders", - "description": "

Likers gender ratio.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_likers_genders.code", - "description": "

Male or Female likers.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_likers_genders.weight", - "description": "

Ratio of gender.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "audience_likers_brand_affinities", - "description": "

Likers brand affinity ratio.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_likers_brand_affinities.name", - "description": "

Name of brand.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_likers_brand_affinities.weight", - "description": "

Ratio of brand.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "audience_likers_brand_affinities.affinity", - "description": "

Likers brand affinity.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "audience_likers_interests", - "description": "

Likers interests.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_likers_interests.name", - "description": "

Name of the interest.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_likers_interests.weight", - "description": "

Ratio of interest.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "audience_likers_interests.affinity", - "description": "

Likers interest affinity.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "followers", - "description": "

Number of Instagram followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "avg_likes_per_post", - "description": "

Number of average likes per post.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "avg_commments_per_post", - "description": "

Number of average comments per post.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "engagement_rate", - "description": "

Engagement ratio.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "notable_followers", - "description": "

Notable followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "notable_followers.engagements", - "description": "

Engagement of the follower.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "notable_followers.followers", - "description": "

Number of followers of the follower.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_followers.fullname", - "description": "

Full Name of the follower.

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "notable_followers.is_verified", - "description": "

true if account is verified else false.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_followers.picture", - "description": "

Picture of the follower.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_followers.url", - "description": "

Url of the follower.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "notable_followers.user_id", - "description": "

Notable followers.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "notable_followers.username", - "description": "

Notable followers.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "notable_followers.geo", - "description": "

Location of the follower.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "notable_followers.geo.country", - "description": "

Country of the follower.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "notable_followers.geo.state", - "description": "

State of the follower.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "notable_followers.geo.city", - "description": "

City of the follower.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_followers.geo.country.id", - "description": "

ID of the country (or state or city).

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_followers.geo.country.name", - "description": "

Name of the country (or state or city).

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_followers.geo.country.code", - "description": "

Code of the country (or state or city).

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "notable_followers.geo.country.coords", - "description": "

Geograhic co-ordinates of the country, state or city.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "notable_followers.geo.country.coords.lat", - "description": "

Latitude of country, state or city.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "notable_followers.geo.country.coords.lon", - "description": "

Longitude of country, state or city.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "timestp", - "description": "

Date of the retrieved data in ISO format (YYYY-MM-DD).

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"top_countries\": [\n {\n \"name\": \"United States\",\n \"code\": \"US\",\n \"percent\": \"24.79\",\n \"followers\": 7661481\n },\n ...\n ],\n \"top_cities\": [\n {\n \"name\": \"New York City\",\n \"percent\": \"1.57\",\n \"followers\": 486692,\n \"state\": \"New York\",\n \"country\": \"United States\"\n },\n ...\n ],\n \"likers_top_countries\": [\n {\n \"name\": \"Indonesia\",\n \"code\": \"ID\",\n \"percent\": \"23.94\",\n \"likes\": 881672709\n },\n ...\n ],\n \"likers_top_cities\": [\n {\n \"name\": \"Jakarta\",\n \"percent\": \"1.53\",\n \"likes\": 56389121,\n \"country\": \"Indonesia\"\n },\n ...\n ],\n \"audience_genders_per_age\": [\n {\n \"code\": \"13-17\",\n \"male\": \"3.17\",\n \"female\": \"2.63\"\n },\n ...\n ],\n \"audience_ethnicities\": [\n {\n \"code\": \"white\",\n \"name\": \"White / Caucasian\",\n \"weight\": \"61.00\"\n },\n ...\n ],\n \"audience_genders\": [\n {\n \"code\": \"male\",\n \"weight\": \"64.81\"\n },\n {\n \"code\": \"female\",\n \"weight\": \"35.19\"\n }\n ],\n \"audience_brand_affinities\": [\n {\n \"name\": \"Apple\",\n \"weight\": \"8.64\",\n \"affinity\": 1.015819046\n },\n ...\n ],\n \"audience_interests\": [\n {\n \"name\": \"Friends, Family & Relationships\",\n \"weight\": \"38.40\",\n \"affinity\": 1.16935123\n },\n ...\n ],\n \"audience_likers_genders_per_age\": [\n {\n \"code\": \"13-17\",\n \"male\": \"1.40\",\n \"female\": \"13.74\"\n },\n ...\n ],\n \"audience_likers_ethnicities\": [\n {\n \"code\": \"asian\",\n \"name\": \"Asian\",\n \"weight\": \"42.83\"\n },\n ...\n ],\n \"audience_likers_genders\": [\n {\n \"code\": \"male\",\n \"weight\": \"14.36\"\n },\n ...\n ],\n \"audience_likers_brand_affinities\": [\n {\n \"name\": \"Walt Disney\",\n \"weight\": \"9.17\",\n \"affinity\": 1.17608759\n },\n ...\n ],\n \"audience_likers_interests\": [\n {\n \"name\": \"Music\",\n \"weight\": \"33.75\",\n \"affinity\": 1.288918539\n },\n ...\n ],\n \"followers\": 30905034,\n \"avg_likes_per_post\": 716758,\n \"avg_commments_per_post\": 7136,\n \"engagement_rate\": 2.3423,\n \"notable_followers\": [\n {\n \"user_id\": \"19410587\",\n \"username\": \"kingjames\",\n \"picture\": \"https://imgp.sptds.icu/v2?mb0KwpL92uYofJiSjDn1%2F6peL1lBwv3s%2BUvShHERlDY9ylLT5c6L8M5YYtkm82Y2lmZGj2PuiPxH4MzYyrRo%2FDbQucdCwwRkefwEJsxLB6iDqLP%2B%2BRr5s9nKoSGm1D7gT8Moe9fwKIU9YOWB9c0yTQ%3D%3D\",\n \"followers\": 72077829,\n \"fullname\": \"LeBron James\",\n \"url\": \"https://www.instagram.com/kingjames\",\n \"geo\": {\n \"city\": {\n \"id\": 207359,\n \"name\": \"Los Angeles\",\n \"coords\": {\n \"lat\": 34.05223,\n \"lon\": -118.24368\n }\n },\n \"state\": {\n \"id\": 165475,\n \"name\": \"California\",\n \"coords\": {\n \"lat\": 37.231633,\n \"lon\": -119.664078\n }\n },\n \"country\": {\n \"id\": 148838,\n \"name\": \"United States\",\n \"code\": \"US\",\n \"coords\": {\n \"lat\": 38.89511,\n \"lon\": -77.03637\n }\n }\n },\n \"is_verified\": true,\n \"engagements\": 1865105\n },\n ...\n ],\n \"timestp\": \"2022-04-03\"\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/236/instagram-audience-stats?date=2022-04-03&geoOnly=false", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/instagram-audience-stats/dates", - "title": "Instagram Audience Data Dates", - "version": "1.0.0", - "name": "GetArtistInstagramAudienceStatsDates", - "description": "

Get dates for which there is available Instagram Audience Analysis and Stats data for an artist.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "dates", - "description": "

Array of dates as YYYY-MM-DD strings.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n \"2022-09-12\",\n \"2022-09-11\",\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/2762/instagram-audience-stats/dates", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id", - "title": "Metadata", - "version": "1.0.0", - "name": "GetArtistMetadata", - "description": "

Retrieve the artist's metadata and chartmetric stats. The stat data is cached daily.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Number", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - } - ] - } - }, - "header": { - "fields": { - "Header": [ - { - "group": "Header", - "type": "Boolean", - "optional": true, - "field": "X-Accept-Partial-Data", - "description": "

If set to true, the API will return partial data if the full data is not available. If set to false, the API will return 406 Not Acceptable if the full data is not available.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "created_at", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "allowedValues": [ - "US", - "JP", - "..." - ], - "optional": false, - "field": "code2", - "description": "

Artist Country Code. (Full List in Introduction).

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "band", - "description": "

Band of Artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "pronoun_title", - "description": "

Pronoun title of Artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "gender_title", - "description": "

Gender title of Artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "gender", - "description": "

gender of Artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isni", - "description": "

isni of Artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_artist_rank", - "description": "

Chartmetric Artist Rank

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_artist_score", - "description": "

Chartmetric Artist Score

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cover_url", - "description": "

Artist cover image

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Artist image

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "hometown_city", - "description": "

hometown city of Artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "current_city", - "description": "

current city of Artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "current_city_id", - "description": "

current city of Artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "band_members", - "description": "

band members of Artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "booking_agent", - "description": "

booking agent of Artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "record_label", - "description": "

record label of Artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "press_contact", - "description": "

press contact of Artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "general_manager", - "description": "

general manager of Artist

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "topSongwriterCollaborators", - "description": "

Top Songwriter Collaborators

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "description", - "description": "

Artist description

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "genres", - "description": "

Genres of Artist

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "genres.primary", - "description": "

Primary genre of artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "genres.primary.id", - "description": "

Id of Primary genre

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genres.primary.name", - "description": "

Name of Primary genre

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genres.primary.type", - "description": "

(Internal system value, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "genres.primary.score", - "description": "

(Internal system value, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "genres.secondary", - "description": "

Array of secondary genres of artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "genres.secondary.id", - "description": "

Id of Secondary genre

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genres.secondary.name", - "description": "

Name of Secondary genre

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genres.secondary.type", - "description": "

(Internal system value, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "genres.secondary.score", - "description": "

(Internal system value, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "genres.sub", - "description": "

Array of Subgenres of artist. Genres in detail categorized by Spotify.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "genres.sub.id", - "description": "

Id of subgenre

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genres.sub.name", - "description": "

Name of subgenre

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genres.sub.type", - "description": "

(Internal system value, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "genres.sub.score", - "description": "

(Internal system value, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "genre_smart_ordered", - "description": "

An array of genre objects where the key is the order of the genre and the value is the new genre id.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "moods", - "description": "

Array of moods of artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "moods.id", - "description": "

Id of mood

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "moods.name", - "description": "

Name of mood

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "activities", - "description": "

Array of activities of artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "activities.id", - "description": "

Id of activity

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "activities.name", - "description": "

Name of activity

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "career_status", - "description": "

Artist's career stage and trend information

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "career_status.stage", - "description": "

Artist's current career stage as shown on the dashboard.

Possible values in order: undiscovered, developing, mid-level, mainstream, superstar, legendary

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "career_status.stage_score", - "description": "

Value from 0 to 100 representing Artist's relative progression through their current stage.

For example, a score of 100 in the undiscovered stage is directly adjacent to a score of 0 in the developing stage.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "career_status.trend", - "description": "

Artist's current career trend category as shown on the dashboard.

Possible values in order: decline, gradual decline, steady, growth, explosive growth.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "career_status.trend_score", - "description": "

Value from 0 to 100 representing the relative strength of the Artist's trend within their trend category.

For example, a score of 100 in the steady category is directly adjacent to a score of 0 in the growth category.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "cm_statistics", - "description": "

Cm_statistics for the Artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.sp_followers", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.sp_followers_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.deezer_fans", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.deezer_fans_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.sp_popularity", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.sp_popularity_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.sp_monthly_listeners", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.sp_monthly_listeners_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "cm_statistics.sp_where_people_listen", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.sp_where_people_listen.code2", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.sp_where_people_listen.listeners", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.sp_where_people_listen.name", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "sp_where_people_listen.name", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "sp_where_people_listen.code2", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "sp_where_people_listen.listeners", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.ins_followers", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.ins_followers_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.ycs_subscribers", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.ycs_subscribers_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.ycs_views", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.ycs_views_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.tiktok_followers", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.tiktok_followers_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.tiktok_likes", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.tiktok_likes_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.tiktok_top_video_views", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.tiktok_top_video_views_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.tiktok_top_video_comments", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.tiktok_top_video_comments_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.tiktok_track_posts", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.tiktok_track_posts_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.twitter_followers", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.twitter_followers_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.line_music_artist_likes", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.line_music_artist_likes_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.line_music_likes", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.line_music_likes_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.line_music_plays", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.line_music_plays_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.line_music_mv_plays", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.cm_artist_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.fan_base_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.engagement_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.youtube_daily_video_views", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.youtube_daily_video_views_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.youtube_monthly_video_views", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.youtube_monthly_video_views_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.twitch_followers", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.twitch_followers_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.twitch_views", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.twitch_views_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.twitch_monthly_viewer_hours", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.twitch_monthly_viewer_hours_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.twitch_weekly_viewer_hours", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.twitch_weekly_viewer_hours_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.pandora_listeners_28_day", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.pandora_listeners_28_day_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.pandora_lifetime_stations_added", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.pandora_lifetime_stations_added_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.pandora_lifetime_streams", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.pandora_lifetime_streams_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.boomplay_ranking_current", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.boomplay_ranking_current_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.boomplay_favorites", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.boomplay_favorites_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.boomplay_shares", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.boomplay_shares_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.boomplay_comments", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.boomplay_comments_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.boomplay_plays", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.boomplay_plays_rank", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_sp_editorial_playlists", - "description": "

Number of Spotify editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_sp_playlists", - "description": "

Number of Spotify playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.sp_playlist_total_reach", - "description": "

Sum of followers from all Spotify playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.sp_editorial_playlist_total_reach", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_am_editorial_playlists", - "description": "

Number of Apple Music editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_am_playlists", - "description": "

Number of Apple Music playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_de_editorial_playlists", - "description": "

Number of Deezer editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_de_playlists", - "description": "

Number of Deezer playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.de_playlist_total_reach", - "description": "

Sum of fans from all Deezer playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.de_editorial_playlist_total_reach", - "description": "" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_az_editorial_playlists", - "description": "

Number of Amazon editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_az_playlists", - "description": "

Number of Amazon playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_yt_editorial_playlists", - "description": "

Number of YouTube editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.num_yt_playlists", - "description": "

Number of YouTube playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.yt_playlist_total_reach", - "description": "

Number of YouTube playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.yt_editorial_playlist_total_reach", - "description": "

Number of YouTube editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.shazam_count", - "description": "

Number of Shazam plays

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.genius_pageviews", - "description": "

Number of Genius page views

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "cm_statistics.genreRank", - "description": "

Artist's top genre ranking stats

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.genreRank.name", - "description": "

Name of top genre

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.genreRank.rank", - "description": "

Rank of top genre

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "cm_statistics.subGenreRank1", - "description": "

Artist's first top sub genre ranking stats

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.subGenreRank1.name", - "description": "

Name of first top sub genre

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.subGenreRank1.rank", - "description": "

Rank of first top sub genre

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "cm_statistics.subGenreRank2", - "description": "

Artist's second top sub genre ranking stats

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.subGenreRank2.name", - "description": "

Name of second top sub genre

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.subGenreRank2.rank", - "description": "

Rank of second top sub genre

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "cm_statistics.countryRank", - "description": "

Artist's country ranking stats

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.countryRank.country", - "description": "

Name of country

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_statistics.countryRank.rank", - "description": "

Rank of country

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"id\": 2000,\n \"name\": \"Nina Sky\",\n \"created_at\": \"2025-10-30T15:45:02Z\",\n \"code2\": \"US\",\n \"band\": true,\n \"pronoun_title\": \"they/them\",\n \"gender_title\": null,\n \"gender\": null,\n \"isni\": \"0000000109438445\",\n \"cm_artist_rank\": 4505,\n \"cm_artist_score\": 86.1487799428101,\n \"cover_url\": \"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/37/a9/c6/37a9c658-8dbc-1d35-881c-afe798f96943/413b2199-8d1b-433e-9224-b2e175a8ea37.png/160x160bb.jpg\",\n \"image_url\": \"https://i.scdn.co/image/ab6761610000e5eb99130a511f82caacd3fecc03\",\n \"hometown_city\": \"Queens\",\n \"current_city\": null,\n \"current_city_id\": null,\n \"band_members\": null,\n \"booking_agent\": null,\n \"record_label\": \"Glossy Vinyl\",\n \"press_contact\": null,\n \"general_manager\": null,\n \"topSongwriterCollaborators\": [\n \"ルイス・ディアス\",\n \"Lionel Bermingham\"\n ],\n \"description\": \"Born and raised in New York City, twin sisters Nicole and Natalie Albino also known as Nina Sky, entered the Billboard Charts in 2004 with their hit song "Move Ya Body." It was an impactful debut landing at number 4 on the Billboard Hot 100. That same year, the sistersʼ infectious chorus on NORE's reggaeton anthem “Oye Mi Canto,” propelled the song to crossover success. Since their debut, they've established their names not only as singers and songwriters but well-respected DJʼs providing the soundscape for festivals and events worldwide.\\nNina Sky collaborators include Diplo, Major Lazer, Sean Paul, Salaam Remi, Pitbull, and The Alchemist among others. As DJʼs, they've curated sets for The Museum of Modern Art (MOMA), the GLAAD Awards, Dolce & Gabanna, MAC, and more. Their DJ sets are energetic and high spirited meshing multiple genres with a genuine connection to the audience. \\nWhile 2024 marks 20 years since Nicole and Natalie released their debut, the authenticity they embody as artists has continued to create a boundless stream of opportunity. With experience, self-awareness, and an array of musical projects under their belt, the twins continue to connect with their fans on a massive level.\",\n \"genres\": {\n \"primary\": {\n \"id\": 501120,\n \"name\": \"pop\",\n \"type\": \"genre_v2\",\n \"source\": \"cm\"\n },\n \"secondary\": [\n {\n \"id\": 501125,\n \"name\": \"r&b/soul\",\n \"type\": \"genre_v2\",\n \"source\": \"cm\"\n },\n {\n \"id\": 501146,\n \"name\": \"dance\",\n \"type\": \"genre_v2\",\n \"source\": \"cm\"\n }\n ],\n \"sub\": [\n {\n \"id\": 9122,\n \"name\": \"hip pop\",\n \"type\": \"genre\",\n \"source\": \"spotify\"\n }\n ]\n },\n \"genre_smart_ordered\": [\n {\n \"1\": 501120\n },\n {\n \"2\": 501125\n },\n {\n \"3\": 501146\n },\n {\n \"4\": 501124\n },\n {\n \"5\": 501773\n },\n {\n \"6\": 501399\n },\n {\n \"7\": 503537\n },\n {\n \"8\": 503515\n },\n {\n \"9\": 503521\n },\n {\n \"10\": 503511\n },\n {\n \"11\": 503516\n },\n {\n \"12\": 503528\n },\n {\n \"13\": 9122\n }\n ],\n \"moods\": [\n {\n \"id\": 504927,\n \"name\": \"celebratory\"\n },\n {\n \"id\": 504954,\n \"name\": \"energetic\"\n },\n {\n \"id\": 504978,\n \"name\": \"fun\"\n },\n {\n \"id\": 504956,\n \"name\": \"entertaining\"\n },\n {\n \"id\": 505080,\n \"name\": \"upbeat\"\n },\n {\n \"id\": 504961,\n \"name\": \"exciting\"\n },\n {\n \"id\": 504909,\n \"name\": \"affectionate\"\n },\n {\n \"id\": 504952,\n \"name\": \"empowering\"\n }\n ],\n \"activities\": [\n {\n \"id\": 504872,\n \"name\": \"dancy\"\n },\n {\n \"id\": 504876,\n \"name\": \"partying\"\n },\n {\n \"id\": 504878,\n \"name\": \"urban\"\n },\n {\n \"id\": 504884,\n \"name\": \"summer\"\n },\n {\n \"id\": 504886,\n \"name\": \"drinking\"\n },\n {\n \"id\": 504871,\n \"name\": \"bonding\"\n },\n {\n \"id\": 504903,\n \"name\": \"late-night\"\n },\n {\n \"id\": 504880,\n \"name\": \"vacation\"\n }\n ],\n \"career_status\": {\n \"stage\": \"mainstream\",\n \"stage_score\": 42,\n \"trend\": \"decline\",\n \"trend_score\": 96\n },\n \"cm_statistics\": {\n \"countryRank\": {\n \"code2\": \"US\",\n \"country\": \"United States\",\n \"rank\": 1,\n \"percentile\": 1\n },\n \"sp_followers\": 147460505,\n \"sp_followers_rank\": 2,\n \"deezer_fans\": 12223141,\n \"deezer_fans_rank\": 7,\n \"sp_popularity\": 100,\n \"sp_popularity_rank\": 1,\n \"sp_monthly_listeners\": 106103812,\n \"sp_monthly_listeners_rank\": 5,\n \"sp_where_people_listen\": [\n {\n \"code2\": \"id\",\n \"listeners\": 2465588,\n \"name\": \"Jakarta\"\n },\n {\n \"code2\": \"gb\",\n \"listeners\": 2152620,\n \"name\": \"London\"\n },\n {\n \"code2\": \"ph\",\n \"listeners\": 1425039,\n \"name\": \"Quezon City\"\n },\n {\n \"code2\": \"mx\",\n \"listeners\": 1412425,\n \"name\": \"Mexico City\"\n },\n {\n \"code2\": \"au\",\n \"listeners\": 1243194,\n \"name\": \"Sydney\"\n }\n ],\n \"ins_followers\": 281520373,\n \"ins_followers_rank\": 5,\n \"ycs_subscribers\": 62700000,\n \"ycs_subscribers_rank\": 10,\n \"ycs_views\": 42637641606,\n \"ycs_views_rank\": 9,\n \"tiktok_followers\": 33300000,\n \"tiktok_followers_rank\": 41,\n \"tiktok_likes\": 264600000,\n \"tiktok_likes_rank\": 433,\n \"tiktok_top_video_views\": 83262479720,\n \"tiktok_top_video_views_rank\": 6,\n \"tiktok_top_video_comments\": 81558905,\n \"tiktok_track_posts\": 51918089,\n \"tiktok_track_posts_rank\": 16,\n \"cm_artist_rank\": 1,\n \"cm_artist_score\": 99.38394291752742,\n \"fan_base_rank\": 1,\n \"rank_fb\": 1,\n \"rank_eg\": 1,\n \"engagement_rank\": 1,\n \"twitter_followers\": 78777877,\n \"youtube_daily_video_views\": 16096296,\n \"youtube_daily_video_views_rank\": 13,\n \"youtube_monthly_video_views\": 483595207,\n \"youtube_monthly_video_views_rank\": 11,\n \"twitch_followers\": null,\n \"twitch_followers_rank\": null,\n \"twitch_views\": null,\n \"twitch_views_rank\": null,\n \"twitch_monthly_viewer_hours\": null,\n \"twitch_monthly_viewer_hours_rank\": null,\n \"twitch_weekly_viewer_hours\": null,\n \"twitch_weekly_viewer_hours_rank\": null,\n \"pandora_listeners_28_day\": 3498495,\n \"pandora_listeners_28_day_rank\": 52,\n \"pandora_lifetime_stations_added\": 50025257,\n \"pandora_lifetime_stations_added_rank\": 6,\n \"pandora_lifetime_streams\": 12727916580,\n \"pandora_lifetime_streams_rank\": 11,\n \"boomplay_ranking_current\": null,\n \"boomplay_ranking_current_rank\": null,\n \"boomplay_favorites\": null,\n \"boomplay_favorites_rank\": null,\n \"boomplay_shares\": null,\n \"boomplay_shares_rank\": null,\n \"boomplay_comments\": null,\n \"boomplay_comments_rank\": null,\n \"boomplay_plays\": null,\n \"boomplay_plays_rank\": null,\n \"num_sp_editorial_playlists\": 1257,\n \"num_sp_playlists\": 1820462,\n \"sp_playlist_total_reach\": 1011368609,\n \"sp_editorial_playlist_total_reach\": 214939061,\n \"num_am_editorial_playlists\": 1137,\n \"num_am_playlists\": 2878,\n \"num_de_editorial_playlists\": 244,\n \"num_de_playlists\": 1191,\n \"de_playlist_total_reach\": 63405591,\n \"de_editorial_playlist_total_reach\": 34260255,\n \"num_az_editorial_playlists\": 1534,\n \"num_az_playlists\": 1630,\n \"num_yt_editorial_playlists\": 201,\n \"num_yt_playlists\": 948,\n \"yt_playlist_total_reach\": 13308245921,\n \"yt_editorial_playlist_total_reach\": 1409147885,\n \"shazam_count\": 244928447,\n \"genius_pageviews\": 198089148,\n \"line_music_artist_likes\": 687457,\n \"line_music_likes\": 1092973,\n \"line_music_likes_rank\": 52,\n \"line_music_plays\": 137078906,\n \"line_music_plays_rank\": 64,\n \"line_music_mv_plays\": 137078906\n },\n \"genreRank\": {},\n \"subGenreRank1\": {},\n \"subGenreRank2\": {}\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/2000", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/milestones", - "title": "Milestone", - "version": "1.0.0", - "name": "GetArtistMilestones", - "description": "

Get Artist’s milestone information.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

The number of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Array", - "allowedValues": [ - "1", - "2", - "3", - "4", - "5" - ], - "optional": true, - "field": "stars", - "description": "

Filter base on stars of data from current data. (e.g. [1, 2, 3])

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "daysAgo", - "description": "

Filter base on artists first release based on number of days ago from current date.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "date", - "stars" - ], - "optional": true, - "field": "sortColumn", - "defaultValue": "date", - "description": "

Sort by column.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "sortOrderDesc", - "defaultValue": "true", - "description": "

Whether to sort in descending order.

" - }, - { - "group": "Parameter", - "type": "Array", - "allowedValues": [ - "amazon", - "apple_music", - "beatport", - "chartmetric", - "deezer", - "itunes", - "pandora", - "shazam", - "soundcloud", - "spotify", - "tiktok", - "youtube" - ], - "optional": true, - "field": "platforms", - "defaultValue": "true", - "description": "

Filter base on platforms from current data.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "allowedValues": [ - "false" - ], - "optional": true, - "field": "grouped", - "description": "

Whether to group similar milestones together or not, based on group.

" - } - ] - } - }, - "success": { - "fields": { - "grouped = false": [ - { - "group": "grouped = false", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

ID of the milestone.

" - }, - { - "group": "grouped = false", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Date of milestone.

" - }, - { - "group": "grouped = false", - "type": "String", - "optional": false, - "field": "summary", - "description": "

Summary of the milestone, matching what can be found on the aritst profile page.

" - }, - { - "group": "grouped = false", - "type": "String", - "optional": false, - "field": "artist_name", - "description": "

Name of artist.

" - }, - { - "group": "grouped = false", - "type": "String", - "optional": false, - "field": "track_name", - "description": "

Name of track.

" - }, - { - "group": "grouped = false", - "type": "String", - "optional": false, - "field": "platform", - "description": "

Platform of milestone.

" - }, - { - "group": "grouped = false", - "type": "String", - "optional": false, - "field": "stars", - "description": "

Stars of milestone.

" - } - ], - "grouped = true": [ - { - "group": "grouped = true", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

ID of the milestone.

" - }, - { - "group": "grouped = true", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Date of milestone.

" - }, - { - "group": "grouped = true", - "type": "String", - "optional": false, - "field": "summary", - "description": "

Summary of the milestone, matching what can be found on the aritst profile page.

" - }, - { - "group": "grouped = true", - "type": "String", - "optional": false, - "field": "artist_name", - "description": "

Name of artist.

" - }, - { - "group": "grouped = true", - "type": "String", - "optional": false, - "field": "track_name", - "description": "

Name of track.

" - }, - { - "group": "grouped = true", - "type": "String", - "optional": false, - "field": "platform", - "description": "

Platform of milestone.

" - }, - { - "group": "grouped = true", - "type": "String", - "optional": false, - "field": "stars", - "description": "

Stars of milestone.

" - }, - { - "group": "grouped = true", - "type": "Integer", - "optional": false, - "field": "group_size", - "description": "

Size of each group.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response (grouped = false):", - "content": "HTTP/1.1 200 OK\n{\n\t\"obj\": {\n\t\t\"insights\": [\n\t\t\t{\n\t\t\t\t\"id\": 1754976786,\n\t\t\t\t\"date\": \"2024-04-29\",\n\t\t\t\t\"summary\": \"Run added to Spotify Top 50 - Vietnam at position 5 with 90.5K followers.\",\n\t\t\t\t\"artist_name\": \"BTS\",\n\t\t\t\t\"track_name\": \"Run\",\n\t\t\t\t\"platform\": \"Spotify\",\n\t\t\t\t\"stars\": 4\n\t\t\t},\n ...\n ],\n \"count\": 3126\n }\n}", - "type": "json" - }, - { - "title": "Success-Response (grouped = true):", - "content": "HTTP/1.1 200 OK\n{\n\t\"obj\": {\n\t\t\"insights\": [\n\t\t\t{\n\t\t\t\t\"id\": 1754976786,\n\t\t\t\t\"date\": \"2024-04-29\",\n\t\t\t\t\"summary\": \"Run added to Spotify Top 50 - Vietnam at position 5 with 90.5K followers.\",\n\t\t\t\t\"artist_name\": \"BTS\",\n\t\t\t\t\"track_name\": \"Run\",\n\t\t\t\t\"platform\": \"Spotify\",\n\t\t\t\t\"stars\": 4,\n\t\t\t\t\"group_size\": 1\n\t\t\t},\n ...\n ],\n \"count\": 1024\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage (grouped = false):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/206557/milestones?platforms[]=spotify&stars[]=4", - "type": "curl" - }, - { - "title": "Example usage (grouped = true):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/206557/milestones?platforms[]=spotify&stars[]=4&grouped=true", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/news", - "title": "News", - "version": "1.0.0", - "name": "GetArtistNews", - "description": "

Get Artist’s recent milestones in a news format

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID value to get future events.

" - }, - { - "group": "Parameter", - "type": "Integer", - "size": "0-50", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

Number of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Integer", - "size": "0-10", - "optional": true, - "field": "weight", - "defaultValue": "5", - "description": "

Weight of the milestone. 2 weights means 1 star

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

ID of the news.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "type", - "description": "

Entity type of the news.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "subType", - "description": "

Type of sub-entity of the news.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "formattedDate", - "description": "

Formatted date of the news.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "imageUrl", - "description": "

URL of the news image.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "summaryTemplate", - "description": "

Template for the summary text.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "summaryData", - "description": "

Data used in the summary template.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "summaryData.chartName", - "description": "

Name of the chart.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "summaryData.playlistName", - "description": "

Name of the playlist.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "summaryData.trackName", - "description": "

Name of the track.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "metadata", - "description": "

Additional metadata related to the news.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "metadata.playlist_id", - "description": "

ID of the playlist.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "metadata.playlistUrlParam", - "description": "

URL parameter for the playlist.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "metadata.cmTrackId", - "description": "

Chartmetric track ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "metadata.artistName", - "description": "

Name of the artist.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "metadata.cmArtistId", - "description": "

Chartmetric artist ID.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "stars", - "description": "

Star rating of the news.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response: past", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": \"1\",\n \"type\": \"artist\",\n \"subType\": \"playlist\",\n \"formattedDate\": \"2025-02-21\",\n \"imageUrl\": \"https://i.scdn.co/image/ab67616d0000b27317db30ce3f081d6818a8ad49\",\n \"summaryTemplate\": \":trackName added to Spotify :playlistName at position 1 with 17.2K followers.\",\n \"summaryData\": {\n \"playlistName\": \"BAD NIGHTAN\",\n \"trackName\": \"Run BTS\"\n },\n \"metadata\": {\n \"playlist_id\": \"16165930\",\n \"playlistUrlParam\": \"playlist/spotify?id=16165930\",\n \"cmTrackId\": 83623672,\n \"artistName\": \"BTS\",\n \"cmArtistId\": 206557\n },\n \"stars\": 3\n },\n ...\n ,\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/2762/news", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/noteworthy-insights", - "title": "Noteworthy Insights", - "version": "1.0.0", - "name": "GetArtistNoteworthyInsights", - "description": "

Get Artist Noteworthy Insights

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "5", - "description": "

Number of entries to be returned

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "daily", - "weekly" - ], - "optional": true, - "field": "insightType", - "defaultValue": "daily", - "description": "

Type of insight to be returned

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "fromDaysAgo", - "defaultValue": "28", - "description": "

Number of days ago to start the insights from

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "variant", - "description": "

Variant of the insight, e.g. very-high, high, low, very-low

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "insightType", - "description": "

Type of insight, e.g. daily, weekly

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "metricName", - "description": "

Name of the metric of the insight, e.g.spotify_followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "insightId", - "description": "

Insight ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "target", - "description": "

Target of the insight, always "artist" in this APIi

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "targetName", - "description": "

Name of the target artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "targetImageUrl", - "description": "

Image URL of the target artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "insightDate", - "description": "

Date of the insight

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "insightDateMinus7Days", - "description": "

Date before 7 days of insight date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "graphImageUrl", - "description": "

Image URL of the noteworthy insight graph

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "metricDx1", - "description": "

Actual metric value of the metric for the insight

" - }, - { - "group": "Success 200", - "type": "Double", - "optional": false, - "field": "metricDx2", - "description": "

Growth percentage of the target metric,multiply by 100 to get percentage

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n {\n \"obj\": [\n {\n \"variant\": \"very-low\",\n \"insightType\": \"daily\",\n \"metricName\": \"sp_followers\",\n \"insightId\": 6765723,\n \"target\": \"cm_artist\",\n \"targetId\": \"5596\",\n \"targetName\": \"Billie Eilish\",\n \"targetImageUrl\": \"https://i.scdn.co/image/ab67616100005174d8b9980db67272cb4d2c3daf\",\n \"insightDate\": \"2024-10-19T07:00:00.000Z\",\n \"insightDateMinus7Days\": \"2024-10-12T00:00:00.000Z\",\n \"graphImageUrl\": \"https://chartmetric-public.s3.us-west-2.amazonaws.com/noteworthy_insights/daily/dashboard/2024-10-19/cm_artist/5596/sp_followers.jpeg\",\n \"metricDx1\": 93203,\n \"metricDx2\": 0.9780343385895287\n },\n {\n \"variant\": \"low\",\n \"insightType\": \"daily\",\n \"metricName\": \"ins_followers\",\n \"insightId\": 6762560,\n \"target\": \"cm_artist\",\n \"targetId\": \"5596\",\n \"targetName\": \"Billie Eilish\",\n \"targetImageUrl\": \"https://i.scdn.co/image/ab67616100005174d8b9980db67272cb4d2c3daf\",\n \"insightDate\": \"2024-10-18T07:00:00.000Z\",\n \"insightDateMinus7Days\": \"2024-10-11T00:00:00.000Z\",\n \"graphImageUrl\": \"https://chartmetric-public.s3.us-west-2.amazonaws.com/noteworthy_insights/daily/dashboard/2024-10-18/cm_artist/5596/ins_followers.jpeg\",\n \"metricDx1\": 83890,\n \"metricDx2\": 1.5836952169761926\n },\n {\n \"variant\": \"very-low\",\n \"insightType\": \"daily\",\n \"metricName\": \"sp_followers\",\n \"insightId\": 6747816,\n \"target\": \"cm_artist\",\n \"targetId\": \"5596\",\n \"targetName\": \"Billie Eilish\",\n \"targetImageUrl\": \"https://i.scdn.co/image/ab67616100005174d8b9980db67272cb4d2c3daf\",\n \"insightDate\": \"2024-10-12T07:00:00.000Z\",\n \"insightDateMinus7Days\": \"2024-10-05T00:00:00.000Z\",\n \"graphImageUrl\": \"https://chartmetric-public.s3.us-west-2.amazonaws.com/noteworthy_insights/daily/dashboard/2024-10-12/cm_artist/5596/sp_followers.jpeg\",\n \"metricDx1\": 91781,\n \"metricDx2\": 0.9892282017382259\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/5596/noteworthy-insights?limit=10&insightType=daily&fromDaysAgo=90", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/riaa", - "title": "Get Artist RIAA Awards", - "version": "1.0.0", - "name": "GetArtistRIAA", - "description": "

Get artist albums with RIAA awards.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_album", - "description": "

Chartmetric's album ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "upc", - "description": "

Album UPC.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "certification_date", - "description": "

Date the album was awarded. String in the format YYYY-MM-DDT00:00:00.000Z

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "award", - "description": "

Award name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album_title", - "description": "

Album title.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "format", - "description": "

Album format. Possigle options include: single, album, video single, among others.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Album cover URL.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "label", - "description": "

Album label.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"riaa_awards\": [\n {\n \"cm_album\": 9384,\n \"upc\": \"602557276077\",\n \"certification_date\": \"2018-06-29T00:00:00.000Z\",\n \"award\": \"1x Platinum\",\n \"album_title\": \"Fake Love\",\n \"format\": \"single\",\n \"image_url\": \"https://i.scdn.co/image/...\",\n \"label\": \"Cash Money Records/Motown Records\"\n },\n {\n \"cm_album\": 9384,\n \"upc\": \"602557276077\",\n \"certification_date\": \"2018-03-08T00:00:00.000Z\",\n \"award\": \"4x Multi-Platinum\",\n \"album_title\": \"Fake Love\",\n \"format\": \"single\",\n \"image_url\": \"https://i.scdn.co/image/...\",\n \"label\": \"Cash Money Records/Motown Records\"\n },\n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/3380/riaa", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/artist-rank", - "title": "Artist Rank", - "version": "1.0.0", - "name": "GetArtistRank", - "description": "

Get artist rank within our artist list given a certain metric

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "sp_popularity", - "sp_followers", - "sp_monthly_listeners", - "sp_listeners_to_followers_ratio", - "deezer_fans", - "cm_artist_rank", - "fs_likes", - "fs_talks", - "ts_followers", - "ts_retweets", - "ins_followers", - "ycs_views", - "ycs_subscribers", - "ws_views", - "soundcloud_followers", - "bs_followers" - ], - "optional": true, - "field": "metric", - "defaultValue": "cm_artist_rank", - "description": "" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "US", - "JP", - "..." - ], - "optional": true, - "field": "code2", - "description": "

Country code. By default no country filter is applied. (Full list in introduction)

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "genre", - "description": "

Chartmetric genre id, by default no genre filter is applied

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artist_rank", - "description": "

Artist rank

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n artist_rank: 1094\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/3380/artist-rank?metric=fs_likes", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/tvmaze", - "title": "TV Show Appearances", - "version": "1.0.0", - "name": "GetArtistTVmaze", - "description": "

Get Artist’s TV appearances information

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "people", - "description": "

Artist info

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "people.id", - "description": "

Tvmaze show id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "people.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "people.gender", - "description": "

gender

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "people.image", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "people.date_of_birth", - "description": "

Date of birth

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "people.date_of_death", - "description": "

Date of death

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "people.country_code", - "description": "

country code

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "people.tv_maze_url", - "description": "

tv maze url

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "people.cm_artist", - "description": "

Chartmetric artist id

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "people.created_at", - "description": "

created date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "people.modified_at", - "description": "

modified date

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "shows", - "description": "

list of show

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "shows.title", - "description": "

title

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "shows.as", - "description": "

role

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "shows.url", - "description": "

tvmaze url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "shows.year", - "description": "

year

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "shows.episodes", - "description": "

list of episodes

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "shows.episodes.title", - "description": "

title

" - }, - { - "group": "Success 200", - "type": "string", - "optional": false, - "field": "shows.episodes.url", - "description": "

link to the show

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "shows.episodes.date", - "description": "

episode date

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "shows.show_details", - "description": "

show details

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "shows.show_details.id", - "description": "

show id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "shows.show_details.name", - "description": "

show name

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "shows.show_details.genres", - "description": "

genres

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "shows.show_details.runtime", - "description": "

runtime

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "shows.show_details.premiered", - "description": "

premiered date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "shows.show_details.officialsite", - "description": "

officialsite

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "shows.show_details.rating", - "description": "

rating

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "shows.show_details.summary", - "description": "

summary

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "shows.show_details.tv_maze_url", - "description": "

link of the show

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "shows.show_details.created_at", - "description": "

created date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "shows.show_details.modified_at", - "description": "

modified date

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"people\": {\n \"id\": 64614,\n \"name\": \"Ed Sheeran\",\n \"gender\": 1,\n \"image\": \"http://static.tvmaze.com/uploads/images/original_untouched/13/34295.jpg\",\n \"date_of_birth\": \"1991-02-01T08:00:00.000Z\",\n \"date_of_death\": null,\n \"country_code\": \"GB\",\n \"tv_maze_url\": \"http://www.tvmaze.com/people/64614/ed-sheeran\",\n \"cm_artist\": 3648,\n \"created_at\": null,\n \"modified_at\": \"2020-11-12T18:09:59.730Z\"\n },\n \"shows\": [\n {\n \"title\": \"Modern Love\",\n \"as\": \"Guest starring as Mick\",\n \"url\": \"https://www.tvmaze.com/shows/37120/modern-love\",\n \"year\": \"2019\",\n \"episodes\": [\n {\n \"title\": \"Episode 1x07: Hers Was a World of One (Oct 18, 2019)\",\n \"url\": \"https://www.tvmaze.com/episodes/1718414/modern-love-1x07-hers-was-a-world-of-one\",\n \"date\": \"Oct 18, 2019\"\n }\n ],\n \"show_details\": {\n \"id\": 37120,\n \"name\": \"Modern Love\",\n \"genres\": [\n \"Comedy\",\n \"Romance\"\n ],\n \"runtime\": null,\n \"premiered\": \"2019-10-18T07:00:00.000Z\",\n \"officialsite\": \"https://www.amazon.com/dp/B07VMF8TL9/\",\n \"rating\": \"7.5\",\n \"image\": \"http://static.tvmaze.com/uploads/images/original_untouched/216/540345.jpg\",\n \"summary\": \"

An unlikely friendship. A lost love resurfaced. A marriage at its turning point. A date that might not have been a date. An unconventional new family. These are unique stories about the joys and tribulations of love, each inspired by a real-life personal essay from the beloved New York Times column \\\"Modern Love.\\\"

\",\n \"tv_maze_url\": \"http://www.tvmaze.com/shows/37120/modern-love\",\n \"created_at\": \"2020-09-23T22:03:25.001Z\",\n \"modified_at\": \"2020-11-18T00:36:13.551Z\"\n }\n },\n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/3648/tvmaze", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/tiktok-audience-stats", - "title": "TikTok Audience Data", - "version": "1.0.0", - "name": "GetArtistTiktokAudienceStats", - "description": "

Get latest TikTok Audience Analysis and Stats for an artist.

Note: We check for new reports daily and update the corresponding audience data only when a new report is present in its API. Most audience reports are updated on a weekly basis, so we should have at least one update per month for this data. However, when using the date parameter, some dates will return empty response structures. An empty response means that the data didn't receive an update for that date.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "defaultValue": "today", - "description": "

Historical date, in ISO date format (e.g. "2020-09-20"). Data may not be available for all historical dates.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "top_countries", - "description": "

Top countries of Artist.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "top_countries.name", - "description": "

Artist country name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "top_countries.code", - "description": "

Artist country code.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "top_countries.percent", - "description": "

Percentage of followers in the country.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "top_countries.followers", - "description": "

Number of followers in the country.

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "audience_genders_per_age", - "description": "

Audience Gender.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_genders_per_age.code", - "description": "

Age range of audience.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_genders_per_age.male", - "description": "

Male ratio for this age range.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_genders_per_age.female", - "description": "

Female ratio for this age range.

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "audience_genders", - "description": "

Audience gender ratio.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_genders.code", - "description": "

Male or Female audience.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_genders.weight", - "description": "

Ratio of gender.

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "notable_followers", - "description": "

Notable followers of this Artist.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_followers.code", - "description": "

Male or Female audience.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_followers.user_id", - "description": "

User ID of the follower.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_followers.username", - "description": "

Username of the follower.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_followers.picture", - "description": "

Picture of the follower.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "notable_followers.followers", - "description": "

Number of followers of the follower.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_followers.fullname", - "description": "

Full Name of the follower.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_followers.url", - "description": "

Url of the follower.

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "notable_followers.geo", - "description": "

Location of the follower.

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "notable_followers.geo.country", - "description": "

Country of the follower.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_followers.geo.country.id", - "description": "

ID of the country.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_followers.geo.country.name", - "description": "

Name of the country.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_followers.geo.country.code", - "description": "

Country code.

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "notable_followers.geo.country.coords", - "description": "

Geograhic co-ordinates.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "notable_followers.geo.country.coords.lat", - "description": "

Latitude.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "notable_followers.geo.country.coords.lon", - "description": "

Longitude.

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "notable_followers.is_verified", - "description": "

true if account is verified else false.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "notable_followers.engagements", - "description": "

Engagement of the follower.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "followers", - "description": "

Total number of followers.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "avg_likes_per_post", - "description": "

Average likes per post.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "avg_commments_per_post", - "description": "

Average comments per post.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "engagement_rate", - "description": "

Engagement rate of the Artist.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"top_countries\": [\n {\n \"name\": \"United States\",\n \"code\": \"US\",\n \"percent\": \"17.99\",\n \"subscribers\": 9479043\n },\n ...\n ],\n \"audience_genders_per_age\": [\n {\n \"code\": \"13-17\",\n \"male\": \"5.89\",\n \"female\": \"2.71\"\n },\n ...\n ],\n \"audience_genders\": [\n {\n \"code\": \"male\",\n \"weight\": \"72.92\"\n },\n {\n \"code\": \"female\",\n \"weight\": \"27.08\"\n }\n ],\n \"notable_followers\": [\n {\n \"user_id\": \"UCcvSMLuj0OcAASPsTyVlzBA\",\n \"username\": \"DanKarChannel\",\n \"custom_name\": \"DanKar\",\n \"picture\": \"https://yt3.ggpht.com/ytc/AKedOLQ129KckPBX2XZ2Os827HlAU39UBZ5v3ImQqWfWzQ=s480-c-k-c0x00ffffff-no-rj\",\n \"fullname\": \"Данкар\",\n \"url\": \"https://www.youtube.com/channel/UCcvSMLuj0OcAASPsTyVlzBA\",\n \"geo\": {\n \"country\": {\n \"id\": 60189,\n \"name\": \"Russia\",\n \"code\": \"RU\",\n \"coords\": {\n \"lat\": 55.75222,\n \"lon\": 37.61556\n }\n }\n },\n \"is_verified\": true,\n \"engagements\": 167549,\n \"avg_likes\": 167549,\n \"avg_views\": 2499180,\n \"subscribers\": 7720000\n },\n ....\n ],\n \"followers\": 42900000,\n \"avg_likes_per_post\": 112574,\n \"avg_commments_per_post\": 5679,\n \"engagement_rate\": 0.2624,\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/5408/tiktok-audience-stats", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/top-tracks/:source", - "title": "Top Tracks by Platform", - "version": "1.0.0", - "name": "GetArtistTopTracksByPlatform", - "description": "

Get top tracks for an artist by platform

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "tiktok" - ], - "optional": false, - "field": "source", - "description": "

The platform to get top tracks for

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

Number of tracks to return. Maximum 100

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "tiktok", - "description": "

Chartmetric internal ID for TikTok

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tags", - "description": "

Track genres

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tiktok_track_id", - "description": "

TikTok's track id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tiktok_url", - "description": "

TikTok's link for the track

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isrc", - "description": "

Track ISRC

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_track", - "description": "

Chartmetric Track ID

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "monthly_diff_percent", - "description": "

Percentage difference in post count over a month for this track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "posts", - "description": "

Track's post count

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Image link for the TikTok track

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audio_url", - "description": "

Track preview url

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "comments", - "description": "

Track's comment count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "views", - "description": "

Track's view pount

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "likes", - "description": "

Track's likes count

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "created_at", - "description": "

TikTok's video date

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "score", - "description": "

Chartmetric track score

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "trend", - "description": "

Array of objects denoting the historical values for the track post counts in ascending order

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "trend.value", - "description": "

Post count for the specific date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "trend.timestp", - "description": "

Date for which the post counts is recorded

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "engagementRate", - "description": "

Track engagement rate

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n\n{\n \"obj\": [\n {\n \"tiktok\": 6059900,\n \"tags\": \"Country\",\n \"tiktok_track_id\": \"Teardrops-On-My-Guitar-Instrumental-w-BG-vocals-6941546823435241474\",\n \"tiktok_url\": \"https://www.tiktok.com/music/Teardrops-On-My-Guitar-Instrumental-w-BG-vocals-6941546823435241474\",\n \"isrc\": \"USCJY0803453\",\n \"cm_track\": 15191242,\n \"monthly_diff_percent\": null,\n \"posts\": null,\n \"name\": \"Teardrops On My Guitar - Instrumental w/ BG vocals\",\n \"image_url\": \"https://p16-sign-sg.tiktokcdn.com/tos-alisg-v-2774/edfcbc86a61148a99dca0f4059495262~720x720.jpeg?x-expires=1646963559&x-signature=q75xEeBo9RQiLPNeJ6DsnuCa58M%3D\",\n \"audio_url\": null,\n \"comments\": 0,\n \"views\": 225,\n \"likes\": 22,\n \"created_at\": \"2022-02-01 20:52:11.000\",\n \"score\": 14,\n \"trend\": [\n {\n \"value\": 529600,\n \"timestp\": \"2020-11-19T00:00:00.000Z\"\n },\n {\n \"value\": 529300,\n \"timestp\": \"2020-11-20T00:00:00.000Z\"\n }, ...\n ],\n \"engagementRate\": 0.09777777777777778\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/2762/top-tracks/tiktok?limit=20", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/tracks", - "title": "Tracks", - "version": "1.0.0", - "name": "GetArtistTracks", - "description": "

Get the tracks by this artist. We also include those tracks where this artist is 'featured'.
Limitation: Since there is no mash-up flag in cm_track_cluster, mash-up tracks are currently treated as original tracks.

Supported Query Param Combinations for filtering version

version_only version_and version_or version_not example
?version_only[]=original&version_only[]=clean
?version_and[]=original&version_and[]=clean
?version_or[]=original&version_or[]=live
?version_not[]=remix&version_not[]=explicit
?version_and[]=original&version_and[]=clean&version_not[]=remix
?version_or[]=original&version_or[]=cover&version_not[]=remix

Track version types

  • original
  • re_release
  • edit
  • remix
  • acoustic
  • alt_ver
  • physical_ver
  • cover
  • live
  • feat_or_collab
  • dub
  • remaster
  • bonus
  • arrangement
  • commentary
  • lofi
  • component
  • karaoke
  • compilation_rerelease
  • explicit
  • clean
  • other
", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

the offset of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "artist_type", - "description": "

Type of artist: main or featured.
Omitting this parameter will return both types of artists.

" - }, - { - "group": "Parameter", - "type": "String[]", - "optional": true, - "field": "version_only", - "description": "

Types of version: Filter tracks where only the specified types are true and others false.
Can't be used with version_and, version_or, and version_not.

" - }, - { - "group": "Parameter", - "type": "String[]", - "optional": true, - "field": "version_and", - "description": "

Types of version: Filter tracks that contain all of the specified types.
Can’t be used with version_or.

" - }, - { - "group": "Parameter", - "type": "String[]", - "optional": true, - "field": "version_or", - "description": "

Types of version: Filter tracks that contain at least one of the specified types.
Can’t be used with version_and.

" - }, - { - "group": "Parameter", - "type": "String[]", - "optional": true, - "field": "version_not", - "description": "

Types of version: Filter tracks that do not contain any of the specified types.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "total", - "description": "

Total number of tracks for the artist in Chartmetric's database

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artist_type", - "description": "

Type of artist, main or featured.

" - }, - { - "group": "Success 200", - "type": "Integer[]", - "optional": false, - "field": "album_ids", - "description": "

Album IDs

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "album_label", - "description": "

Album labels

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "album_names", - "description": "

Album names

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "album_upc", - "description": "

Album UPC

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "amazon_album_ids", - "description": "

Amazon album ID's

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "amazon_track_ids", - "description": "

Amazon track ID's

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "artist_covers", - "description": "

Artist cover image URLs

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "artist_images", - "description": "

Artist image URLs

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "artist_names", - "description": "

Artist names

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_artist", - "description": "

Chartmetric Artist ID's

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_track", - "description": "

Chartmetric track ID

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "code2s", - "description": "

Country codes

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "created_at", - "description": "

Date this entry was created

" - }, - { - "group": "Success 200", - "type": "Integer[]", - "optional": false, - "field": "deezer_album_ids", - "description": "

Deezer album ID's

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "deezer_duration", - "description": "

Deezer track duration

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "deezer_track_ids", - "description": "

Deezer track ID's

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "description", - "description": "

Track description

Note: This field has been deprecated and currently returns only null. It will be removed after March 1st, 2024.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric's Track ID

Note: This field has been deprecated. Please refer to the cm_track field instead. It will be removed after March 1st, 2024.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Track image URL

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isrc", - "description": "

Track ISRC

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "itunes_album_ids", - "description": "

iTunes album ID's

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "itunes_track_ids", - "description": "

iTunes track ID's

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "modified_at", - "description": "

Date this entry was modified

Note: This field has been deprecated and currently returns only null. It will be removed after March 1st, 2024.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track's name

" - }, - { - "group": "Success 200", - "type": "Date[]", - "optional": false, - "field": "release_dates", - "description": "

Track release dates

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "spotify_album_ids", - "description": "

Spotify album ID's

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "spotify_duration_ms", - "description": "

Spotify track duration

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "spotify_track_ids", - "description": "

Spotify track ID's

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "storefronts", - "description": "

Storefronts list

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tags", - "description": "

Comma separated list of genres for the track.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "cm_statistics", - "description": "

Statistics about the track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.cm_track", - "description": "

Chartmetric's Track ID

Note: This field has been deprecated. Please refer to the root cm_track field instead. It will be removed after March 1st, 2024.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.de_playlist_total_reach", - "description": "

Deeer playlist total reach

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_am_editorial_playlists", - "description": "

Apple music editorial playlists count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_am_playlists", - "description": "

Apple music playlists count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_az_editorial_playlists", - "description": "

Amazon editorial playlists count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_az_playlists", - "description": "

Amazon playlists count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_de_editorial_playlists", - "description": "

Deezer editorial playlists count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_de_playlists", - "description": "

Deezer playlists count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_sp_editorial_playlists", - "description": "

Spotify editorial playlists count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_sp_playlists", - "description": "

Spotify playlists count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_tt_videos", - "description": "

TikTok videos count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_yt_editorial_playlists", - "description": "

YouTube editorial playlists count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_yt_playlists", - "description": "

YouTube playlists count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.sp_playlist_total_reach", - "description": "

Spotify playlist total reach

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.sp_popularity", - "description": "

Spotify popularity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.sp_streams", - "description": "

Spotify streams (not available for every track)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.yt_playlist_total_reach", - "description": "

YouTube playlist total reach

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "cm_audio_features", - "description": "

Chartmetric's Audio features

Note: All of the cm_audio_features.* fields have been deprecated. Currently, all of them are hard-coded to return the value 0. The cm_audio_features response field will be removed after March 1st, 2024.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_audio_features.acousticness", - "description": "

Acousticness

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_audio_features.danceability", - "description": "

Danceability

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_audio_features.energy", - "description": "

Energy

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_audio_features.instrumentalness", - "description": "

Instrumentalness

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_audio_features.key", - "description": "

Key

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_audio_features.liveness", - "description": "

Liveness

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_audio_features.loudness", - "description": "

Loudness

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_audio_features.mode", - "description": "

Mode

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_audio_features.speechiness", - "description": "

Speechiness

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_audio_features.tempo", - "description": "

Tempo

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_audio_features.valence", - "description": "

Valence

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "cm_track_cluster", - "description": "

Chartmetric's track cluster (null if standalone track)

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_track_cluster.cluster_id", - "description": "

Cluster ID of the original track

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "cm_track_cluster.track_types", - "description": "

Types of tracks in the cluster (e.g., original, remix, cover, etc.)

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "version_types", - "description": "

Track version types for the current track.
If cm_track_cluster is null, this track is treated as the original version, and all other version types are considered false.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 12570005,\n \"isrc\": \"GBBKS1200020\",\n \"artist_type\": \"featured\",\n \"cm_artist\": [\n 965,\n 3986\n ],\n \"artist_names\": [\n \"Bobby Womack\",\n \"Lana Del Rey\"\n ],\n \"code2s\": [\n \"US\",\n \"US\"\n ],\n \"artist_images\": [\n null,\n null\n ],\n \"artist_covers\": [\n null,\n null\n ],\n \"spotify_track_ids\": [\n \"3Z1C7835mW8HCXECwVT5pB\",\n \"2ZdLePqGqO0tuQMW1vykK0\",\n \"3k2s5xzpLjHXIElci1a46C\",\n \"326xDdz2oCiv88wzdpSIYY\",\n \"01wi82GHedsBUjSKf2hJQ0\",\n \"4bM9y9H3PvZD2gohgSeYNe\",\n \"6CReiKvUp29iBmOnUZiZqW\"\n ],\n \"spotify_album_ids\": [\n \"3Oy5qaBEIzs9KZVDMwvYI5\",\n \"7tJ3tERlaIjX7ZjcsbHGcc\",\n \"3k7rrPN7zRx0pfZ79ruu5c\",\n \"2xdZFGyx3qtbIbBte2l3gd\",\n \"1d0vqL31qzWAWB8cxyfvuP\",\n \"70kL63Lb8aEotFv3LgOWpT\",\n \"7pGBXiTkLKPzznqorET3bG\"\n ],\n \"spotify_duration_ms\": 259906,\n \"itunes_track_ids\": [\n \"609128028\",\n \"522068424\"\n ],\n \"itunes_album_ids\": [\n \"609127619\",\n \"522068118\"\n ],\n \"storefronts\": [\n \"US\",\n \"US\"\n ],\n \"deezer_track_ids\": [\n \"32936061\"\n ],\n \"deezer_album_ids\": [\n 3174991\n ],\n \"deezer_duration\": 258,\n \"amazon_track_ids\": null,\n \"amazon_album_ids\": null,\n \"album_ids\": [\n 140496,\n 369788,\n 1367684,\n 1877427,\n 12934157,\n 12934219\n ],\n \"album_names\": [\n \"The Bravest Man in the Universe\",\n \"City Lounge - The Deep Session (The Finest Music Selection: Deep House, Trip Hop, Downtempo, Cool Tempo, Lounge, Electro)\",\n \"The Bravest Man In The Universe (Spotify Preview)\",\n \"The Bravest Man In The Universe\",\n \"The Bravest Man in the Universe\",\n \"The Bravest Man In the Universe (Expanded Edition)\"\n ],\n \"album_upc\": [\n \"614027871569\",\n \"3596973282194\",\n \"634904057244\",\n \"634904056162\",\n \"634904056155\",\n \"634904156169\"\n ],\n \"album_label\": [\n \"XL Recordings\",\n \"Wagram Music\",\n \"XL\",\n \"XL Recordings\",\n \"XL Recordings\",\n \"XL Recordings\"\n ],\n \"release_dates\": [\n \"2012-06-08T00:00:00.000Z\",\n \"2015-08-14T00:00:00.000Z\",\n \"2012-06-05T00:00:00.000Z\",\n \"2012-06-08T00:00:00.000Z\",\n \"2012-06-01T00:00:00.000Z\",\n \"2012-06-01T00:00:00.000Z\"\n ],\n \"created_at\": \"2025-08-01T04:38:14Z\",\n \"image_url\": \"https://i.scdn.co/image/8feee61973cc047e433e6f150b2be32e354ba503\",\n \"name\": \"Dayglo Reflection\",\n \"genre_smart_ordered\": [\n {\n \"1\": 501160\n },\n {\n \"2\": 501122\n },\n {\n \"3\": 501135\n },\n {\n \"4\": 501132\n },\n {\n \"5\": 507574\n },\n {\n \"6\": 501872\n },\n {\n \"7\": 507814\n },\n {\n \"8\": 501543\n },\n {\n \"9\": 501442\n },\n {\n \"10\": 501124\n },\n {\n \"11\": 507549\n },\n {\n \"12\": 507611\n },\n {\n \"13\": 507748\n },\n {\n \"14\": 501120\n },\n {\n \"15\": 507702\n },\n {\n \"16\": 507851\n },\n {\n \"17\": 507553\n }\n ],\n \"cm_track_cluster\": {\n \"cluster_id\": 12570005,\n \"track_types\": [\n \"original\",\n \"clean\"\n ]\n },\n \"cm_statistics\": {\n \"cm_track\": 12570005,\n \"de_playlist_total_reach\": 6614,\n \"num_am_editorial_playlists\": 0,\n \"num_am_playlists\": 2,\n \"num_az_editorial_playlists\": null,\n \"num_az_playlists\": null,\n \"num_de_editorial_playlists\": 1,\n \"num_de_playlists\": 9,\n \"num_sp_editorial_playlists\": 2,\n \"num_sp_playlists\": 2886,\n \"num_tt_videos\": 104,\n \"num_yt_editorial_playlists\": null,\n \"num_yt_playlists\": null,\n \"sp_playlist_total_reach\": 548058,\n \"sp_popularity\": 40,\n \"sp_streams\": 12517893,\n \"yt_playlist_total_reach\": null\n },\n \"cm_track\": 12570005,\n \"tags\": \"indie pop, rock, psychedelic, alternative, bedroom pop, electropop, soft pop, indie folk, dream pop, electronic, acoustic, dark pop, nu jazz, pop, jangle pop, trip-hop, alternative dance\",\n \"cm_audio_features\": {\n \"acousticness\": 0,\n \"danceability\": 0,\n \"energy\": 0,\n \"instrumentalness\": 0,\n \"key\": 0,\n \"liveness\": 0,\n \"loudness\": 0,\n \"mode\": 0,\n \"speechiness\": 0,\n \"tempo\": 0,\n \"valence\": 0\n },\n \"modified_at\": null,\n \"description\": null,\n \"version_types\": {\n \"original\": true,\n \"re_release\": false,\n \"edit\": false,\n \"remix\": false,\n \"acoustic\": false,\n \"alt_ver\": false,\n \"physical_ver\": false,\n \"cover\": false,\n \"live\": false,\n \"feat_or_collab\": false,\n \"dub\": false,\n \"remaster\": false,\n \"bonus\": false,\n \"arrangement\": false,\n \"commentary\": false,\n \"lofi\": false,\n \"component\": false,\n \"karaoke\": false,\n \"compilation_rerelease\": false,\n \"explicit\": false,\n \"clean\": true,\n \"other\": false\n }\n },\n ],\n \"total\": 443\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/206557/tracks", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/urls", - "title": "Social / Streaming Service URLs", - "version": "1.0.0", - "name": "GetArtistURLs", - "description": "

Get social media URLs belonging to this artist.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "domain", - "description": "

Domain

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "url", - "description": "

Domain url's

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"domain\": \"spotify\",\n \"url\": [\n \"https://open.spotify.com/artist/3TVXtAsR1Inumwj472S9r4\"\n ]\n },\n {\n \"domain\": \"amazon\",\n \"url\": [\n \"https://music.amazon.com/artists/B000QJRIHS\"\n ]\n },\n {\n \"domain\": \"youtubeforartist\",\n \"url\": [\n \"https://charts.youtube.com/artist/%2Fm%2F05mt_q\"\n ]\n },\n {\n \"domain\": \"tvmaze\",\n \"url\": [\n \"http://www.tvmaze.com/people/72635/drake\"\n ]\n },\n {\n \"domain\": \"shazam\",\n \"url\": [\n \"https://www.shazam.com/artist/10100284/drake\"\n ]\n },\n {\n \"domain\": \"soundcloud\",\n \"url\": [\n \"https://soundcloud.com/octobersveryown\"\n ]\n },\n {\n \"domain\": \"genius\",\n \"url\": [\n \"https://genius.com/artists/Drake\"\n ]\n },\n {\n \"domain\": \"musicbrainz\",\n \"url\": [\n \"https://musicbrainz.org/artist/9fff2f8a-21e6-47de-a2b8-7f449929d43f\"\n ]\n },\n {\n \"domain\": \"website\",\n \"url\": [\n \"http://www.drakeofficial.com/\"\n ]\n },\n {\n \"domain\": \"lastfm\",\n \"url\": [\n \"http://www.last.fm/music/Drake\"\n ]\n },\n {\n \"domain\": \"itunes\",\n \"url\": [\n \"https://itunes.apple.com/us/artist/id271256\"\n ]\n },\n {\n \"domain\": \"deezer\",\n \"url\": [\n \"https://www.deezer.com/artist/246791\"\n ]\n },\n {\n \"domain\": \"bandsintown\",\n \"url\": [\n \"https://www.bandsintown.com/a/1488\"\n ]\n },\n {\n \"domain\": \"instagram\",\n \"url\": [\n \"https://www.instagram.com/champagnepapi/?hl=en\"\n ]\n },\n {\n \"domain\": \"songkick\",\n \"url\": [\n \"https://www.songkick.com/artists/556955-drake\"\n ]\n },\n {\n \"domain\": \"gtrends\",\n \"url\": [\n \"https://www.google.com/trends/explore#q=/m/05mt_q\"\n ]\n },\n {\n \"domain\": \"discogs\",\n \"url\": [\n \"https://www.discogs.com/artist/151199-Drake\"\n ]\n },\n {\n \"domain\": \"wikipedia\",\n \"url\": [\n \"http://en.wikipedia.org/wiki/Drake_(entertainer)\"\n ]\n },\n {\n \"domain\": \"twitter\",\n \"url\": [\n \"https://twitter.com/Drake\"\n ]\n },\n {\n \"domain\": \"youtube\",\n \"url\": [\n \"https://www.youtube.com/channel/UCByOQJjav0CUDwxCk-jVNRQ\"\n ]\n },\n {\n \"domain\": \"facebook\",\n \"url\": [\n \"https://www.facebook.com/Drake/\"\n ]\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/3380/urls", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/youtube-audience-stats", - "title": "YouTube Audience Data", - "version": "1.0.0", - "name": "GetArtistYoutubeAudienceStats", - "description": "

Get latest YouTube Audience Analysis and Stats for an artist.

Note: We check for new reports daily and update the corresponding audience data only when a new report is present in its API. Most audience reports are updated on a weekly basis, so we should have at least one update per month for this data. However, when using the date parameter, some dates will return empty response structures. An empty response means that the data didn't receive an update for that date.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "defaultValue": "today", - "description": "

Historical date in ISO date format (e.g. 2020-09-20). Data may not be available for all historical dates. If no date is specified, this endpoint will retrieve data for the most recent available date. The timestp field of the response specifies the retrieved data date.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "geoOnly", - "defaultValue": "false", - "description": "

If set to true, limits the response to top_countries, commenters_top_countries, subscribers, avg_likes_per_post, avg_comments_per_post, engagement_rate, and timestp. The other response fields are omitted. This reduces payload size and reduces response delay.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "top_countries", - "description": "

list of top countries.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "top_countries.name", - "description": "

Country name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "top_countries.code", - "description": "

country code.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "top_countries.percent", - "description": "

subscribers percentage.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "top_countries.subscribers", - "description": "

number of subscribers.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "commenters_top_countries", - "description": "

list of commenters' top countries.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "commenters_top_countries.name", - "description": "

Country name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "commenters_top_countries.code", - "description": "

country code.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "commenters_top_countries.percent", - "description": "

subscribers percentage.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "commenters_top_countries.comments", - "description": "

number of comments.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "audience_genders_per_age", - "description": "

Audience genders per age.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_genders_per_age.code", - "description": "

Age group.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_genders_per_age.male", - "description": "

male percentage.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_genders_per_age.female", - "description": "

female percentage.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "audience_genders", - "description": "

Audience genders.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_genders.code", - "description": "

male or female.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audience_genders.weight", - "description": "

genders percentage.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "commenters_genders_per_age", - "description": "

Commenters genders per age.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "commenters_genders_per_age.code", - "description": "

Age group.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "commenters_genders_per_age.male", - "description": "

male percentage.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "commenters_genders_per_age.female", - "description": "

female percentage.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "commenters_genders", - "description": "

Commenters genders.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "commenters_genders.code", - "description": "

male or female.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "commenters_genders.weight", - "description": "

genders percentage.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "notable_subscribers", - "description": "

Notable subscribers.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_subscribers.user_id", - "description": "

YouTube user id.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_subscribers.username", - "description": "

YouTube username.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_subscribers.custom_name", - "description": "

YouTube custom name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_subscribers.handle", - "description": "

YouTube Handle

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_subscribers.picture", - "description": "

image url.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_subscribers.fullname", - "description": "

fullname.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_subscribers.url", - "description": "

YouTube channel url.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "notable_subscribers.geo", - "description": "

location info.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "notable_subscribers.geo.country", - "description": "

Country

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "notable_subscribers.geo.country.id", - "description": "

Country id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_subscribers.geo.country.name", - "description": "

Name of the country.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_subscribers.geo.country.code", - "description": "

Country code.

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "notable_subscribers.geo.country.coords", - "description": "

Geograhic co-ordinates.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "notable_subscribers.geo.country.coords.lat", - "description": "

Latitude.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "notable_subscribers.geo.country.coords.lon", - "description": "

Longitude.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_subscribers.is_verified", - "description": "

if verified user.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "notable_subscribers.engagements", - "description": "

Engagement of the follower.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "notable_subscribers.avg_likes", - "description": "

Average likes.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "notable_subscribers.avg_views", - "description": "

Average views.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "notable_subscribers.subscribers", - "description": "

subscribers count.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "timestp", - "description": "

Date of the retrieved data in ISO format (YYYY-MM-DD).

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "subscribers", - "description": "

Subscribers count.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "avg_likes_per_post", - "description": "

Average likes per post.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "avg_commments_per_post", - "description": "

Average commments per post.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "engagement_rate", - "description": "

Engagement percentage.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"top_countries\": [\n {\n \"name\": \"United States\",\n \"code\": \"US\",\n \"percent\": \"20.22\",\n \"subscribers\": 8675495\n },\n ...\n ],\n \"commenters_top_countries\": [\n {\n \"name\": \"United States\",\n \"code\": \"US\",\n \"percent\": \"12.53\",\n \"comments\": 2010886\n },\n ...\n ],\n \"audience_genders_per_age\": [\n {\n \"code\": \"13-17\",\n \"male\": \"4.69\",\n \"female\": \"2.63\"\n },\n ...\n ],\n \"audience_genders\": [\n {\n \"code\": \"female\",\n \"weight\": \"27.61\"\n },\n {\n \"code\": \"male\",\n \"weight\": \"72.39\"\n }\n ],\n \"commenters_genders_per_age\": [\n {\n \"code\": \"13-17\",\n \"male\": \"2.64\",\n \"female\": \"12.32\"\n },\n ...\n ],\n \"commenters_genders\": [\n {\n \"code\": \"FEMALE\",\n \"weight\": \"77.37\"\n },\n {\n \"code\": \"MALE\",\n \"weight\": \"22.63\"\n }\n ],\n \"notable_subscribers\": [\n {\n \"user_id\": \"UCeLHszkByNZtPKcaVXOCOQQ\",\n \"username\": \"postmalone\",\n \"picture\": \"https://yt3.ggpht.com/ytc/AAUvwngEccasZINOruGNJrrWPijiYyEs7oNppT9StVJQ4Q=s480-c-k-c0x00ffffff-no-rj\",\n \"fullname\": \"Post Malone\",\n \"url\": \"https://www.youtube.com/channel/UCeLHszkByNZtPKcaVXOCOQQ\",\n \"geo\": {\n \"country\": {\n \"id\": 148838,\n \"name\": \"United States\",\n \"code\": \"US\",\n \"coords\": {\n \"lat\": 38.89511,\n \"lon\": -77.03637\n }\n }\n },\n \"is_verified\": false,\n \"engagements\": 226914,\n \"avg_likes\": 226914,\n \"avg_views\": 12372306,\n \"subscribers\": 20200000\n },\n ...\n ],\n \"subscribers\": 42900000,\n \"avg_likes_per_post\": 112574,\n \"avg_commments_per_post\": 5679,\n \"engagement_rate\": 0.2624,\n \"timestp\": \"2022-03-28\"\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/236/youtube-audience-stats?date=2022-03-28&geoOnly=false", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/market-coverage-views/youtube", - "title": "YouTube Views and Market Coverage", - "version": "1.2.0", - "name": "GetArtistYoutubeMarketCoverageViews", - "description": "

Get Artist YouTube Views and Market Coverage

Note: YouTube data for top 100 cities available prior to May 24, 2024

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "Date", - "description": "

in the format YYYY-MM-DD

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "insights", - "description": "

Insights shows the latest snapshot of monthly YouTube video views by country/city, ordered by the total of views

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "insights.cities", - "description": "

list of cities

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "insights.cities.value", - "description": "

Views by cities or countries

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "insights.cities.timestp", - "description": "

time stamp

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "insights.cities.rank", - "description": "

city rank

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "insights.cities.params_id", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "insights.cities.name", - "description": "

name of city or country

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "insights.cities.lat", - "description": "

latitude

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "insights.cities.lng", - "description": "

longitude

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "insights.cities.code2", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "insights.cities.city_id", - "description": "

chartmetric city id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "insights.cities.current_max_count", - "description": "

maximum value count for cities and countries

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "insights.cities.region", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "insights.cities.market_max_cm_artist_id", - "description": "

Chartmetric artist id with maximum value count

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "insights.cities.market_max_artist_name", - "description": "

Chartmetric artist name maximum value count

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "insights.countries", - "description": "

list of countries

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "marketInsights", - "description": "

Market Insights shows the ratio of views (weight) that an artist has reached over the maximum active views in this country/city, ordered by the weight

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "marketInsights.marketCities", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "marketInsights.marketCities.value", - "description": "

Views at market cities or countries

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "marketCities.marketCities.weight", - "description": "

Market Coverage

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "youtubeforartist_location", - "description": "

YouTube for artist location

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"insights\": {\n \"cities\": [\n {\n \"value\": 4514386,\n \"timestp\": \"2020-12-13T00:00:00.000Z\",\n \"rank\": 1,\n \"params_id\": \"0x89c24fa5d33f083b:0xc80b8f06e177fe62\",\n \"name\": \"New York, NY, USA\",\n \"lat\": 40.6943,\n \"lng\": -73.9249,\n \"code2\": \"US\",\n \"city_id\": 7060,\n \"current_max_count\": \"7691395\",\n \"region\": \"CITY\",\n \"market_max_cm_artist_id\": \"113381\",\n \"market_max_artist_name\": \"Ozuna\"\n },\n ...\n ],\n \"countries\": [\n {\n \"value\": 93391941,\n \"timestp\": \"2020-12-13T00:00:00.000Z\",\n \"rank\": 1,\n \"params_id\": \"0x54eab584e432360b:0x1c3bb99243deb742\",\n \"name\": \"United States\",\n \"lat\": null,\n \"lng\": null,\n \"code2\": null,\n \"city_id\": null,\n \"current_max_count\": \"214369882\",\n \"region\": \"COUNTRY\",\n \"market_max_cm_artist_id\": \"215373\",\n \"market_max_artist_name\": \"YoungBoy Never Broke Again\"\n },\n ...\n ]\n },\n \"marketInsights\": {\n \"marketCities\": [\n {\n \"value\": \"582874\",\n \"weight\": 192.807359357479,\n \"youtubeforartist_location\": \"0x89e3652d0d3d311b:0x787cbf240162e8a0\",\n \"current_max_count\": \"302309\",\n \"youtube_artist_id\": \"/m/09gdrgy\",\n \"region\": \"CITY\",\n \"lat\": 42.3188,\n \"lng\": -71.0846,\n \"code2\": \"US\",\n \"city_id\": 7047,\n \"name\": \"Boston, MA, USA\",\n \"market_max_cm_artist_id\": \"3973\",\n \"market_max_artist_name\": \"Romeo Santos\"\n },\n ...\n ],\n \"marketCountries\": [\n {\n \"value\": \"6962212\",\n \"weight\": 56.5674775345716,\n \"youtubeforartist_location\": \"0x4b0d03d337cc6ad9:0x9968b72aa2438fa5\",\n \"current_max_count\": \"12307800\",\n \"youtube_artist_id\": \"/m/0w68qx3\",\n \"region\": \"COUNTRY\",\n \"lat\": null,\n \"lng\": null,\n \"code2\": null,\n \"city_id\": null,\n \"name\": \"Canada\",\n \"market_max_cm_artist_id\": \"206557\",\n \"market_max_artist_name\": \"BTS\"\n }, ...\n ]\n }\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/3380/market-coverage-views/youtube?date=2020-12-13", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/stat/:source", - "title": "Fan Metrics", - "version": "1.0.0", - "name": "GetArtistorStat", - "description": "

Get artist's fan metrics for various services.

There are two ways to get fan metrics from each source:

  1. Return all fields (by default).
  2. Return only selected field (by adding 'field' parameter).
For each source, here are the respective fields you can query for:
source Valid field values
spotify followers, popularity, listeners
deezer fans
facebook likes, talks
twitter followers, retweets
youtube_channel (only includes artist's channel) subscribers, views, comments, videos
youtube_artist (includes artist's channel and all other videos featuring artist's music) daily_views, monthly_views
instagram followers
wikipedia views
bandsintown followers
soundcloud followers
tiktok followers, likes
twitch follower_count, monthly_viewer_hours, weekly_viewer_hours
line likes
melon fans

Additionally, some metrics have associated geographic information. The following table presents those metrics and which geographic parameters are supported:

\t \t\t \t\t \t \t \t\t \t\t \t\t \t\t \t \t \t\t \t\t \t\t \t\t \t \t \t\t \t\t \t\t \t\t \t \t \t\t \t\t \t\t \t\t \t \t \t\t \t\t \t\t \t\t \t \t \t\t \t\t \t\t \t\t \t
sourcefield Comparable by City (city_id) Comparable by Country (code2)
instagramfollowersYesYes
spotifylistenersYesNo
tiktokfollowersNoYes
youtube_channelsubscribersNoYes
youtube_artistdaily_viewsYesYes
youtube_artistmonthly_viewsYesYes
If using the field parameter, ensure that the field is valid with respect to the source.

For example, /wikipedia?field=views is valid while /wikipedia?field=followers is invalid.

Note that the API response may not return all the data points between the two dates when we are missing the data. They may still show on our UI because we are interpolating the missing data between the two existing data points. See the interpolated: true|false in the response.", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "deezer", - "facebook", - "twitter", - "instagram", - "youtube_channel", - "youtube_artist", - "wikipedia", - "bandsintown", - "soundcloud", - "tiktok", - "twitch" - ], - "optional": false, - "field": "source", - "description": "

Data source

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "since", - "description": "

begining date, in ISO date format (e.g. "2017-03-25").

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

end date, in ISO date format (e.g. "2017-03-25"). Default is today.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "field", - "description": "

Specific stat from source (followers, popularity, listeners, talks, subscribers, etc.)

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date. If this is true, since/until parameters will be ignored

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "interpolated", - "defaultValue": "false", - "description": "

Returns interpolated data for missing data. We use a very simple logic for this - by filling the data with the average difference between the two days.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "isDomainId", - "defaultValue": "false", - "description": "

If true, the id passed in the request parameter will be considered the domain ID.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "code2", - "description": "

ISO code for certain metrics. See Charts - Introduction for a full list of possible values. May not be defined if city_id is defined.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "city_id", - "description": "

Chartmetric city ID for certain metrics. Use type=city with Search - Chartmetric Search Engine to retrieve possible values. May not be defined if code2 is defined.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "link", - "description": "

Link associated with the artist for the specified domain if present

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "followers", - "description": "

Statistics on followers

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "popularity", - "description": "

Statistics on popularity

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "listeners", - "description": "

Statistics on listeners

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "followers_to_listeners_ratio", - "description": "

Statistics on the differences between followers and listeners

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "value", - "description": "

Value of metric requested

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "weekly_diff", - "description": "

The value difference between today and 7 days ago for the field.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "weekly_diff_percent", - "description": "

The percentage difference between today and 7 days ago for the field.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "monthly_diff", - "description": "

The value difference between today and 28 days ago for the field.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "monthly_diff_percent", - "description": "

The percentage difference between today and 28 days ago for the field.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "daily_diff", - "description": "

This field is called "diff" if interpolated is set to false. Daily Difference in Value (not applicable for Spotify popularity)

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "timestp", - "description": "

time stamp

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "interpolated", - "description": "

False by default. If true, this value did not come straight from the source, but was rather interpolated based on the previous/next values (not applicable for Spotify popularity and Wikipedia views)

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"link\": \"https://open.spotify.com/artist/4gzpq5DPGxSnKTe4SA8HAU\",\n \"followers\": [\n {\n \"weekly_diff\": 100339,\n \"weekly_diff_percent\": 0.3637,\n \"monthly_diff\": 343512,\n \"monthly_diff_percent\": 1.2562,\n \"value\": 23806554,\n \"timestp\": \"2020-01-03T08:00:00.000Z\",\n \"diff\": 17652\n },\n ...\n ],\n \"popularity\": [\n {\n \"weekly_diff\": 0,\n \"weekly_diff_percent\": 0,\n \"monthly_diff\": 1,\n \"monthly_diff_percent\": 1.1236,\n \"value\": 93,\n \"timestp\": \"2020-01-01T08:00:00.000Z\"\n },\n ...\n ],\n \"listeners\": [\n {\n \"weekly_diff\": -239091,\n \"weekly_diff_percent\": -0.6747,\n \"monthly_diff\": 343076,\n \"monthly_diff_percent\": 0.9843,\n \"value\": 41400238,\n \"timestp\": \"2020-01-01T08:00:00.000Z\",\n \"diff\": null\n },\n ...\n ],\n \"followers_to_listeners_ratio\": [\n {\n \"weekly_diff\": 0.008138000003,\n \"weekly_diff_percent\": 1.0453315251,\n \"monthly_diff\": 0.002111999988,\n \"monthly_diff_percent\": 0.2692040493,\n \"value\": \"57.41\",\n \"timestp\": \"2020-01-01T08:00:00.000Z\"\n },\n ...\n ]\n }\n}", - "type": "json" - }, - { - "title": "Success-Response (Selected Field 'popularity' Usage):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"link\": \"https://open.spotify.com/artist/4gzpq5DPGxSnKTe4SA8HAU\",\n \"popularity\": [\n {\n \"weekly_diff\": 0,\n \"weekly_diff_percent\": 0,\n \"monthly_diff\": 1,\n \"monthly_diff_percent\": 1.1236,\n \"value\": 93,\n \"timestp\": \"2020-01-01T08:00:00.000Z\"\n },\n ...\n ]\n }\n}", - "type": "json" - }, - { - "title": "Success-Response (Spotify w/ isDomainId):", - "content": " {\n \"obj\": {\n \"link\": \"https://open.spotify.com/artist/07YZf4WDAMNwqr4jfgOZ8y\",\n \"followers\": [\n {\n \"weekly_diff\": 0,\n \"weekly_diff_percent\": 0,\n \"monthly_diff\": 0,\n \"monthly_diff_percent\": 0,\n \"value\": 84,\n \"timestp\": \"2019-12-31T08:00:00.000Z\"\n },\n ...\n ],\n \"popularity\": [\n {\n \"weekly_diff\": 0,\n \"weekly_diff_percent\": 0,\n \"monthly_diff\": 0,\n \"monthly_diff_percent\": 0,\n \"value\": 84,\n \"timestp\": \"2019-12-31T08:00:00.000Z\"\n },\n ...\n ],\n \"listeners\": [\n {\n \"weekly_diff\": -884410,\n \"weekly_diff_percent\": -1.8776,\n \"monthly_diff\": -1892049,\n \"monthly_diff_percent\": -3.9327,\n \"value\": 18571413,\n \"timestp\": \"2019-12-31T08:00:00.000Z\",\n \"diff\": null\n },\n ...\n ],\n \"followers_to_listeners_ratio\": [\n {\n \"weekly_diff\": 0.003997999999,\n \"weekly_diff_percent\": 2.248568632,\n \"monthly_diff\": 0.009541999992,\n \"monthly_diff_percent\": 5.5393653659,\n \"value\": \"39.13\",\n \"timestp\": \"2019-12-31T08:00:00.000Z\"\n },\n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "All Fields usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/439/stat/spotify?since=2020-01-01&until=2020-01-12", - "type": "curl" - }, - { - "title": "Selected Field usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/439/stat/spotify?field=popularity&since=2020-01-01&until=2020-01-12", - "type": "curl" - }, - { - "title": "Example usage (Spotify w/ isDomainId):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/07YZf4WDAMNwqr4jfgOZ8y/stat/spotify?since=2020-01-01&until=2020-01-12&isDomainId=true", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/neighboring-artists", - "title": "Neighboring Artists", - "version": "1.2.0", - "name": "GetGenreClusterNeighboringArtists", - "description": "

Get other artists that are similar to this artist in related genres.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "sp_popularity", - "sp_followers", - "sp_monthly_listeners", - "sp_listeners_to_followers_ratio", - "deezer_fans", - "cm_artist_rank", - "fs_likes", - "fs_talks", - "ts_followers", - "ts_retweets", - "ins_followers", - "ycs_views", - "ycs_subscribers", - "ws_views", - "soundcloud_followers", - "bs_followers" - ], - "optional": true, - "field": "metric", - "defaultValue": "cm_artist_rank", - "description": "" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

the number of entries to be returned; default - 10, max - 100

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "type", - "description": "

Should be set to genre if genre-clustering should be applied

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "cluster_artists", - "description": "

Cluster of neighboring artists

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cluster_artists.rank", - "description": "

Current rank.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.position", - "description": "

Current position

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.sp_popularity", - "description": "

Spotify popularity.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.sp_followers", - "description": "

Spotify ollowers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.deezer_fans", - "description": "

Deezer fans.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.sp_monthly_listeners", - "description": "

Spotify monthly listeners.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "cluster_artists.sp_where_people_listen", - "description": "

Spotify listener count and location.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cluster_artists.sp_where_people_listen.code2", - "description": "

Country codes.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.sp_where_people_listen.listeners", - "description": "

listener count .

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cluster_artists.sp_where_people_listen.name", - "description": "

Location of listeners .

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "cluster_artists.sp_listeners_to_followers_ratio", - "description": "

Spotify listeners to followers ratio.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "cluster_artists.sp_followers_to_listeners_ratio", - "description": "

Spotify followers to listeners ratio.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.cm_artist_rank", - "description": "

Chartmetric artist rank.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.fs_likes", - "description": "

Facebook likes.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.fs_talks", - "description": "

Facebook talks.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.ts_followers", - "description": "

Twitter followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.ts_retweets", - "description": "

Twitter retweets.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.ins_followers", - "description": "

Instagram followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.ycs_views", - "description": "

YouTube channel views.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.ycs_subscribers", - "description": "

YouTube subscribers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.ws_views", - "description": "

Wikipedia views.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.soundcloud_followers", - "description": "

Soundcloud followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.bs_followers", - "description": "

BandsInTown followers.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "cluster_artists.latest_album_release_date", - "description": "

latest album release date.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "cluster_artists.latest_album_upc", - "description": "

latest album upc.

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "cluster_artists.signed", - "description": "" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.tiktok_likes", - "description": "

TikTok likes.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.tiktok_followers", - "description": "

TikTok followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.youtube_daily_video_views", - "description": "

YouTube daily video views.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.youtube_monthly_video_views", - "description": "

YouTube monthly video views.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.twitch_followers", - "description": "

Twitch followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.twitch_views", - "description": "

Twitch views

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.twitch_monthly_viewer_hours", - "description": "

Twitch monthly viewer hours

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.twitch_weekly_viewer_hours", - "description": "

Twitch weekly viewer hours

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.pandora_listeners_28_day", - "description": "

Pandora listeners 28 day count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cluster_artists.pandora_stations_added", - "description": "

Pandora stations added count

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cluster_artists.pandora_lifetime_streams", - "description": "

Pandora lifetime streams count

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cluster_artists.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cluster_artists.image_url", - "description": "

Artist image

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cluster_artists.genres", - "description": "

list of genres

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "tags", - "description": "

Tags of genres

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "tags.top_genres", - "description": "

Top genres

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"cluster_artists\": [\n {\n \"rank\": \"1\",\n \"position\": 2,\n \"id\": 3380,\n \"sp_popularity\": 100,\n \"sp_followers\": 44757304,\n \"deezer_fans\": \"19490817\",\n \"sp_monthly_listeners\": 52434550,\n \"sp_where_people_listen\": [\n {\n \"code2\": \"us\",\n \"listeners\": 1188676,\n \"name\": \"Chicago\"\n },\n ...\n ],\n \"sp_listeners_to_followers_ratio\": 1.018039212,\n \"sp_followers_to_listeners_ratio\": 0.982280435,\n \"cm_artist_rank\": 7,\n \"fs_likes\": 34970596,\n \"fs_talks\": 19182,\n \"ts_followers\": 39255042,\n \"ts_retweets\": 0,\n \"ins_followers\": 72837961,\n \"ycs_views\": \"8945632525\",\n \"ycs_subscribers\": 23400000,\n \"ws_views\": 183,\n \"soundcloud_followers\": 2680619,\n \"bs_followers\": 6241017,\n \"latest_album_release_date\": \"2020-08-14T00:00:00.000Z\",\n \"latest_album_upc\": \"602435131771\",\n \"signed\": true,\n \"tiktok_likes\": null,\n \"tiktok_followers\": null,\n \"youtube_daily_video_views\": \"7554656\",\n \"youtube_monthly_video_views\": \"193087939\",\n \"twitch_followers\": null,\n \"twitch_views\": null,\n \"twitch_monthly_viewer_hours\": null,\n \"twitch_weekly_viewer_hours\": null,\n \"pandora_listeners_28_day\": 9638002,\n \"pandora_stations_added\": 2657,\n \"pandora_lifetime_stations_added\": 82620482,\n \"pandora_lifetime_streams\": \"24644067373\",\n \"name\": \"Drake\",\n \"image_url\": \"https://i.scdn.co/image/60cfab40c6bb160a1906be45276829d430058005\",\n \"genres\": \"rap, canadian pop, hip hop, pop rap, canadian hip hop, toronto rap\"\n }, ...\n ],\n \"tags\": {\n \"top_genres\": [\n \"'hip hop'\",\n \"'rap'\",\n \"'pop rap'\",\n \"'southern hip hop'\",\n \"'trap music'\"\n ]\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/3380/neighboring-artists?metric=fs_likes&limit=10", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/past-artist-rank", - "title": "Historical Artist Rank", - "version": "1.0.0", - "name": "GetHistoricalArtistRank", - "description": "

Get artist rank within our artist list given a certain metric on a certain date.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "sp_popularity", - "sp_followers", - "sp_monthly_listeners", - "sp_listeners_to_followers_ratio", - "deezer_fans", - "cm_artist_rank", - "cm_artist_country_rank", - "cm_artist_genre_rank", - "cm_artist_subgenre_rank", - "fs_likes", - "fs_talks", - "ts_followers", - "ts_retweets", - "ins_followers", - "ycs_views", - "ycs_subscribers", - "ws_views", - "soundcloud_followers", - "bs_followers" - ], - "optional": true, - "field": "metric", - "defaultValue": "cm_artist_rank", - "description": "" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "defaultValue": "today", - "description": "

Historical date in ISO date format (e.g. "2021-09-11"). Data may not be available for all historical dates.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artist_rank", - "description": "

Artist rank

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "code2", - "description": "

Country code for the artist's country rank (cm_artist_country_rank only)

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "genre_id", - "description": "

Chartmetric genre id for the artist's genre rank (cm_artist_genre_rank only)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Chartmetric genre name

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"artist_rank\": 1,\n \"genre_id\": 188,\n \"genre\": \"pop\"\n },\n {\n \"artist_rank\": 1,\n \"genre_id\": 419154,\n \"genre\": \"uk pop\"\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/3648/past-artist-rank?metric=cm_artist_subgenre_rank&date=2021-09-11", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/:platform/:status/playlists", - "title": "Playlists", - "version": "1.0.0", - "name": "GetPlaylistsByArtists", - "description": "

Get current or past playlists for an artist.

Each platform and status combination has different sorting options due to the variety of data available. Valid combinations are provided in the below table for your convenience:

platform status Valid sortColumn values
amazon current added_at (default), countries, name, peak_position, track
applemusic current added_at (default), name, peak_position, position, track
applemusic past added_at, name, peak_position, position, removed_at (default), track
deezer current added_at (default), fdiff_month, followers, name, peak_position, track
deezer past added_at, fdiff_month, followers, name, peak_position, removed_at (default), track
spotify current added_at, code2, fdiff_month, followers (default), name, peak_position, position, track
spotify past added_at, code2, fdiff_month, followers (default), name, peak_position, position, removed_at, track
youtube current added_at (default), name, peak_position, track, vdiff_month, views
youtube past added_at, name, peak_position, removed_at (default), track, vdiff_month, views

Parameter options for each platform

Each platform supports different parameters. Valid parameters are provided in the below table for your convenience:

Parameters spotify applemusic deezer amazon
editorial
editorialBrand
personalized
deezerPartner
chart
thisIs
hundredPercent
newMusicFriday
radio
fullyPersonalized
brand
majorCurator
musicBrand
nonMusicBrand
popularIndie
indie
audiobook
personalityArtist

Allowed Parameters Summary:

  • spotify: editorial, personalized, chart, thisIs, newMusicFriday, radio, fullyPersonalized, brand, majorCurator, popularIndie, indie, audiobook
  • applemusic: editorial, editorialBrand, chart, radio, musicBrand, nonMusicBrand, indie, personalityArtist
  • deezer: editorial, deezerPartner, chart, hundredPercent, brand, majorCurator, popularIndie, indie
  • amazon: No allowed flags

Important: When no parameters specified, this API can return an empty array. API users should specify parameters explicitly to get data.

Parameters are not mutually exclusive.

Specify other parameters as FALSE to retrieve data only specific to one parameter. (ex. only newMusicFriday)

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "applemusic", - "deezer", - "amazon", - "youtube" - ], - "optional": false, - "field": "platform", - "description": "

Streaming platform type

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "current", - "past" - ], - "optional": false, - "field": "status", - "description": "

Playlist status

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "since", - "defaultValue": "180 days ago", - "description": "

since beginning of the added/removed date, in ISO date format (e.g. "2017-03-25"). The default value will be 6 months prior to the date of call.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

end of the added/removed date, in ISO date format (e.g. "2017-03-25").

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "indie", - "description": "

If the playlist was curated by major labels

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "editorial", - "defaultValue": "true", - "description": "

If true, the response includes playlists curated by the platform (e.g. Spotify Editorial Playlists, Apple Music Editorial Playlists, Deezer Editorial Playlists). API results match the app only when this parameter is set to true. Spotify includes "personalized" playlists when editorial is set to true.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "majorCurator", - "description": "

If true, the response includes playlists curated by major curators (e.g. Filtr Colombia, Digster, Deezer MENA editor). API results match the app only when this parameter is set to true.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "newMusicFriday", - "description": "

A curated selection of Spotify's most anticipated new tracks of the week.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "editorialBrand", - "description": "

If true, includes playlists curated under specific editorial brands or themes by Apple Music's editorial team.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "personalized", - "description": "

If true, the response includes personalized playlists created based on user's listening habits and preferences.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "deezerPartner", - "description": "

If true, the response includes playlists curated by Deezer partners.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "chart", - "description": "

If true, the response includes playlists based on music charts.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "thisIs", - "description": "

If true, the response includes "This Is" playlists curated by Spotify.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "hundredPercent", - "description": "

If true, the response includes "100&" playlists curated by Deezer

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "radio", - "description": "

If true, the response includes playlists from radio stations.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "fullyPersonalized", - "description": "

If true, the response includes fully personalized playlists curated by Spotify based on the user's listening behavior and preferences

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "brand", - "description": "

If true, the response includes playlists related to brands.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "musicBrand", - "description": "

If true, the response includes playlists curated under specific music brands by Apple Music's editorial team.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "nonMusicBrand", - "description": "

If true, the response includes playlists curated under specific non-music brands by Apple Music's editorial team

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "popularIndie", - "description": "

If true, the response includes playlists featuring popular indie tracks.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "audiobook", - "description": "

If true, the response includes audiobook playlists curated by Spotify. These playlists feature audiobook content.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "personalityArtist", - "description": "

If true, the response includes playlists curated under specific personality artists by Apple Music's editorial team

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "sortColumn", - "description": "

Sort by column. See table above for allowed values

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "sortOrderDesc", - "description": "

Whether to sort in descending order. Can only be defined when sortColumn is defined

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "playlist", - "description": "

Playlist details

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.playlist_id", - "description": "

Spotify's playlist ID (from URI)

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.track_id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.peak_position", - "description": "

The peak position of the track in the playlist

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.position", - "description": "

The position of the track in the playlist

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.cm_track", - "description": "

Track's Chartmetric ID

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.fdiff_month", - "description": "

Difference in number of followers compared to previous month, indicating monthly follower growth

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "playlist.added_at", - "description": "

Added at date

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.period", - "description": "

Period

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.followers", - "description": "

Most recent follower count

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.track", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.name", - "description": "

Playlist Title

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.description", - "description": "

Playlist description

" - }, - { - "group": "Success 200", - "type": "Sttring", - "optional": false, - "field": "playlist.image_url", - "description": "

Playlist cover image URL

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "playlist.sys_last_updated", - "description": "

System last updated date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "playlist.last_updated", - "description": "

Last update date for this playlist

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "playlist.personalized", - "description": "

Personalized status

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.code2", - "description": "

Country

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.curator_name", - "description": "

Curator name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.owner_id", - "description": "

Chartmetric's curator ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.user_id", - "description": "

User ID

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "playlist.editorial", - "description": "

If this playlist is curated by an official editorial team. Applies only to Spotify, Apple Music, and Deezer platforms.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "playlist.tags", - "description": "

Playlist tags

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.tags.id", - "description": "

Tags ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.tags.name", - "description": "

Tags name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.num_track", - "description": "

Total number of tracks in playlist

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.fdiff_week", - "description": "

Difference in number of followers compared to previous week, indicating weekly follower growth

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.catalog", - "description": "

Playlist catalog

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "playlist.active_ratio", - "description": "

Active ratio of playlist

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "track", - "description": "

Track info

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track.id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.isrc", - "description": "

Track's isrc

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.spotify_album_id", - "description": "

Track's Spotify album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.image_url", - "description": "

Track cover image URL

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track.spotify_popularity", - "description": "

The track's Spotify popularity stat

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track.cm_track", - "description": "

Track's Chartmetric ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_artist_names", - "description": "

List of Spotify artist names

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.track_genre", - "description": "

List of genre for the track

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.cm_artist", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.artist_names", - "description": "

Names of artists

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.code2s", - "description": "

Country code of artist

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.artist_images", - "description": "

Image URL of artists

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.artist_covers", - "description": "

Cover image of artists

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_artist_ids", - "description": "

List of Spotify artist IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_track_ids", - "description": "

List of track IDs from Spotify

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_album_ids", - "description": "

List of album IDs from Spotify

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_duration_ms", - "description": "

Spotify duration in milliseconds

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_ids", - "description": "

Album IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_names", - "description": "

Name of album

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_upc", - "description": "

Album upc

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.release_dates", - "description": "

Release date of track

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "track.score", - "description": "

Chartmetric score

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"playlist\": {\n \"playlist_id\": \"24rm8nmbUoNGOPmHuHo82X\",\n \"track_id\": 130396669,\n \"peak_position\": 335,\n \"position\": 335,\n \"cm_track\": 111516408,\n \"fdiff_month\": 0,\n \"added_at\": \"2023-10-04\",\n \"period\": 19,\n \"followers\": 1,\n \"track\": \"Bad Girls\",\n \"name\": \"Линочке ❤🎸🤘\",\n \"playlist_image_url\": \"https://image-cdn-ak.spotifycdn.com/image/ab67706c0000bebba87504063ea6446e82dff60f\",\n \"code2\": null,\n \"curator_name\": \"Влад\",\n \"id\": 15853738,\n \"description\": \"Леечка, Воронка, Фазан, Салями, Сальфеджио, Пипидастр, Вот Это Вот...\",\n \"image_url\": \"https://image-cdn-ak.spotifycdn.com/image/ab67706c0000bebba87504063ea6446e82dff60f\",\n \"sys_last_updated\": \"2023-10-19T00:00:00.000Z\",\n \"last_updated\": \"2023-10-19T20:26:31.000Z\",\n \"personalized\": false,\n \"owner_name\": \"Влад\",\n \"owner_id\": 24259895,\n \"user_id\": \"5xb0g5od6z0chmrv5z9txji2d\",\n \"editorial\": false,\n \"tags\": [\n {\n \"id\": 462884,\n \"name\": \"Rock\"\n },\n {\n \"id\": 462888,\n \"name\": \"Alternative\"\n },\n {\n \"id\": 462882,\n \"name\": \"Pop\"\n }\n ],\n \"num_track\": 344,\n \"fdiff_week\": 0,\n \"catalog\": \"mixed\",\n \"active_ratio\": 0.06686\n },\n \"track\": {\n \"id\": 130396669,\n \"name\": \"Bad Girls\",\n \"isrc\": \"QZWDD2334631\",\n \"spotify_track_id\": \"5U6zdFSc5WJWr1E3O1f3T1\",\n \"spotify_album_id\": \"1W0rzr5HFU9b5hp8TMjk3T\",\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b273302fe5d2572cb5def4a5f9f2\",\n \"spotify_popularity\": 0,\n \"cm_track\": 111516408,\n \"spotify_artist_names\": [\n \"Lvvrs\"\n ],\n \"track_genre\": null,\n \"cm_artist\": [\n 1022311\n ],\n \"artist_names\": [\n \"Lvvrs\"\n ],\n \"code2s\": [\n \"US\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/ab67616100005174e9691106aa3d936390cc799e\"\n ],\n \"artist_covers\": [\n null\n ],\n \"spotify_artist_ids\": [\n \"6VJa7asxiDZyqRa94XNRLA\"\n ],\n \"spotify_track_ids\": [\n \"5U6zdFSc5WJWr1E3O1f3T1\"\n ],\n \"spotify_album_ids\": [\n \"1W0rzr5HFU9b5hp8TMjk3T\"\n ],\n \"spotify_duration_ms\": 175798,\n \"album_ids\": [\n 30925539\n ],\n \"album_names\": [\n \"Bad Girls\"\n ],\n \"album_upc\": [\n \"197773755869\"\n ],\n \"album_label\": [\n \"LVVRS\"\n ],\n \"release_dates\": [\n \"2023-09-15\"\n ],\n \"composer_name\": null,\n \"album\": [\n {\n \"id\": 30925539,\n \"label\": \"LVVRS\",\n \"name\": \"Bad Girls\",\n \"release_date\": \"2023-09-15\",\n \"upc\": \"197773755869\"\n }\n ],\n \"duration_ms\": 175798,\n \"explicit\": false,\n \"score\": 2,\n \"artists\": [\n {\n \"code2\": \"US\",\n \"id\": 1022311,\n \"name\": \"Lvvrs\"\n }\n ]\n }\n },\n ...\n ],\n}", - "type": "json" - }, - { - "title": "Success-Response (only newMusicFriday):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"playlist\": {\n \"playlist_id\": \"37i9dQZF1DXe5W6diBL5N4\",\n \"track_id\": 127071975,\n \"peak_position\": 1,\n \"position\": 169,\n \"cm_track\": 104252070,\n \"fdiff_month\": 4884,\n \"added_at\": \"2023-06-22\",\n \"period\": 25,\n \"followers\": 181046,\n \"track\": \"Zero (J.I.D Remix)\",\n \"name\": \"New Music K-Pop\",\n \"playlist_image_url\": \"https://i.scdn.co/image/ab67706f00000003083b5ec7b40294feeebe4a6c\",\n \"curator_name\": \"Spotify\",\n \"id\": 2398651,\n \"description\": \"Enjoy the freshest K-Pop new releases (Cover: EXO)\",\n \"image_url\": \"https://i.scdn.co/image/ab67706f00000003083b5ec7b40294feeebe4a6c\",\n \"sys_last_updated\": \"2023-07-17T00:00:00.000Z\",\n \"last_updated\": \"2023-07-14T09:12:48.000Z\",\n \"personalized\": false,\n \"owner_name\": \"Spotify\",\n \"owner_id\": 2,\n \"user_id\": \"spotify\",\n \"editorial\": false,\n \"tags\": [\n {\n \"id\": 462925,\n \"name\": \"K-Pop\"\n },\n {\n \"id\": 462882,\n \"name\": \"Pop\"\n },\n {\n \"id\": 478636,\n \"name\": \"Soundtracks\"\n }\n ],\n \"num_track\": 546,\n \"fdiff_week\": 1684,\n \"catalog\": \"frontline\",\n \"active_ratio\": 1\n },\n \"track\": {\n \"id\": 127071975,\n \"name\": \"Zero (J.I.D Remix)\",\n \"isrc\": \"USA2P2332062\",\n \"spotify_track_id\": \"12Yy0hZrUebGnr3IrppPnW\",\n \"spotify_album_id\": \"45wwQll1Ay4A1Rz79wWuNt\",\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b27366daf96fd3f1a920cd2053e6\",\n \"spotify_popularity\": 56,\n \"cm_track\": 104252070,\n \"spotify_artist_names\": [\n \"NewJeans\",\n \"JID\"\n ],\n \"track_genre\": \"hip hop,pop rap,rap,underground hip hop\",\n \"cm_artist\": [\n 9527296,\n 197670\n ],\n \"artist_names\": [\n \"NewJeans\",\n \"JID\"\n ],\n \"code2s\": [\n \"KR\",\n \"US\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/ab676161000051746199c3c2f414880e2b9077a9\",\n \"https://i.scdn.co/image/ab676161000051748f0270ec23a53f3c1fe91849\"\n ],\n \"artist_covers\": [\n null,\n null\n ],\n \"spotify_artist_ids\": [\n \"6HvZYsbFfjnjFrWF950C9d\",\n \"6U3ybJ9UHNKEdsH7ktGBZ7\"\n ],\n \"spotify_track_ids\": [\n \"12Yy0hZrUebGnr3IrppPnW\"\n ],\n \"spotify_album_ids\": [\n \"45wwQll1Ay4A1Rz79wWuNt\"\n ],\n \"spotify_duration_ms\": 224681,\n \"album_ids\": [\n 27870699\n ],\n \"album_names\": [\n \"Zero (J.I.D Remix)\"\n ],\n \"album_upc\": [\n \"196922522048\"\n ],\n \"album_label\": [\n \"ADOR\"\n ],\n \"release_dates\": [\n \"2023-06-20T00:00:00.000Z\"\n ],\n \"composer_name\": null,\n \"album\": [\n {\n \"id\": 27870699,\n \"label\": \"ADOR\",\n \"name\": \"Zero (J.I.D Remix)\",\n \"release_date\": \"2023-06-20\",\n \"upc\": \"196922522048\"\n }\n ],\n \"duration_ms\": 224681,\n \"explicit\": false,\n \"score\": 72,\n \"artists\": [\n {\n \"code2\": \"KR\",\n \"id\": 9527296,\n \"name\": \"NewJeans\"\n },\n {\n \"code2\": \"US\",\n \"id\": 197670,\n \"name\": \"JID\"\n }\n ]\n }\n },\n ...\n ],\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/1022311/spotify/current/playlists?limit=100&since=2023-06-01&editorial=true&personalized=true&chart=false&thisIs=false&newMusicFriday=true&radio=false&fullyPersonalized=false&brand=true&majorCurator=true&popularIndie=true&indie=true&audiobook=false", - "type": "curl" - }, - { - "title": "Example usage (only newMusicFriday):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/9527296/spotify/current/playlists?indie=false&editorial=false&majorCurator=false&newMusicFriday=true", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/relatedartists", - "title": "Related Artists", - "version": "1.0.0", - "name": "GetRelatedArtists", - "description": "

Get similar artists (fans also listen to). For example, if many of artist A's fans also frequently listen to artist B, and C, then B and C are 'related artists'.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "fromDaysAgo", - "description": "

The start of the date range. The number of days ago from the desired ISO date. When this paramter is used, more info about each artist will be returned.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "toDaysAgo", - "description": "

The end of the date range. The number of days until desired ISO date. This parameter defaults to 0 (current date). When this parameter is used, more info about each artist will be returned.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric's Artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "code2", - "description": "

Artist country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_artist_rank", - "description": "

Artist current (latest date) Chartmetric artist rank

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Artist image url

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_followers", - "description": "

Spotify follower count (latest)

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "popularity", - "description": "

Spotify popularity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank_diff", - "description": "

Chartmetric artist rank difference between the toDaysAgo ISO date and its previous date

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "genres", - "description": "

Artist genres

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "genres.primary", - "description": "

Artist primary genre

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "genres.primary.id", - "description": "

Artist primary genre id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genres.primary.name", - "description": "

Artist primary genre name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genres.primary.link_type", - "description": "

Type of genre

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "genres.primary.score", - "description": "

Genre score

" - }, - { - "group": "Success 200", - "type": "Array[Object]", - "optional": false, - "field": "genres.secondary", - "description": "

Artist secondary genre

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "genres.secondary.id", - "description": "

Artist secondary genre id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genres.secondary.name", - "description": "

Artist secondary genre name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genres.secondary.link_type", - "description": "

Type of genre

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "genres.secondary.score", - "description": "

Genre score

" - }, - { - "group": "Success 200", - "type": "Array[Object]", - "optional": false, - "field": "genres.sub", - "description": "

Artist sub genres

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "genres.sub.id", - "description": "

Artist sub genre id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genres.sub.name", - "description": "

Artist sub genre name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genres.sub.link_type", - "description": "

Type of genre

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "genres.sub.score", - "description": "

Genre score

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "rank_stats", - "description": "

Historical Chartmetric Artist rank stats

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank_stats.rank", - "description": "

Chartmetric Artist rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "rank_stats.timestp", - "description": "

Date of Chartmetric Artist rank

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 113381,\n \"name\": \"Ozuna\",\n \"code2\": \"pr\",\n \"cm_artist_rank\": 52,\n \"image_url\": \"https://i.scdn.co/image/ab67616100005174b34caaa3f41eaf9ff5fe849d\",\n \"sp_followers\": 32851167,\n \"popularity\": 85,\n \"rank_diff\": 0,\n \"genres\": {\n \"primary\": {\n \"id\": 462958,\n \"name\": \"Latin Hip-Hop/Rap\",\n \"link_type\": \"cm_artist_genre\",\n \"score\": 5\n },\n \"secondary\": [\n {\n \"id\": 462957,\n \"name\": \"Latin Pop\",\n \"link_type\": \"cm_artist_genre\",\n \"score\": 4\n },\n {\n \"id\": 462896,\n \"name\": \"Reggae\",\n \"link_type\": \"cm_artist_genre\",\n \"score\": 2\n },\n {\n \"id\": 462951,\n \"name\": \"Latin\",\n \"link_type\": \"cm_artist_genre\",\n \"score\": 1\n }\n ],\n \"sub\": [\n {\n \"id\": 402145,\n \"name\": \"trap latino\",\n \"link_type\": \"cm_artist_sub_genre\",\n \"score\": 0\n },\n {\n \"id\": 430403,\n \"name\": \"puerto rican pop\",\n \"link_type\": \"cm_artist_sub_genre\",\n \"score\": 0\n }\n ]\n },\n \"rank_stats\": [\n {\n \"rank\": 52,\n \"timestp\": \"2022-07-29\"\n },\n {\n \"rank\": 52,\n \"timestp\": \"2022-07-28\"\n }\n ]\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/214945/relatedartists?limit=5&fromDaysAgo=7&toDaysAgo=0", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/similar-artists/by-configurations", - "title": "Similar Artists by Configurations", - "version": "1.0.0", - "name": "GetSimilarArtistsByConfigurations", - "description": "

Get similar artists for this artist by configurations.
The target artist is always included as the first result.
The query must include at least one of the following parameters: [audience, mood, genre, musicality].

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

Number of similar artists to return

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Number of similar artists to skip

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "audience", - "description": "

Similarity of Audience between artist
Allowed values: high, medium, low

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "mood", - "description": "

Similarity of Mood between artist
Allowed values: high, medium, low

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "genre", - "description": "

Similarity of Genre between artist
Allowed values: high, medium, low

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "musicality", - "description": "

Similarity of Musicality between artist
Allowed values: high, medium, low

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Artist image URL

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "code2", - "description": "

Artist country code

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "verified", - "description": "

Chartmetric verified artist

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "gender", - "description": "

Artist gender

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "band", - "description": "

true if the artist profile is for a band

" - }, - { - "group": "Success 200", - "type": "Array[Number]", - "optional": false, - "field": "genres", - "description": "

Artist genres

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "genres_smart_ordered", - "description": "

An array of genre objects where the key is the order of the genre and the value is the new genre id

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "subgenres", - "description": "

An array of subgenre objects

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

Artist rank

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "score", - "description": "

Chartmetric score

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "career_stage", - "description": "

Artist's current career stage shown on the dashboard
Possible values in order: undiscovered, developing, mid-level, mainstream, superstar, legendary

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "recent_momentum", - "description": "

Artist's recent momentum shown on the dashboard
Possible values in order: decline, gradual decline, steady, growth, explosive growth.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "similarity", - "description": "

Similarity score

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_followers", - "description": "

Spotify followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_monthly_listeners", - "description": "

Spotify monthly listeners

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "spotify_playlist_total_reach", - "description": "

Spotify playlist total reach

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_playlist_count", - "description": "

Spotify playlist count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "youtube_subscribers", - "description": "

YouTube subscribers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "youtube_monthly_video_views", - "description": "

YouTube monthly video views

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "ins_followers", - "description": "

Instagram followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "tiktok_followers", - "description": "

TikTok followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank_eg", - "description": "

Engagement rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank_fb", - "description": "

Fanbase rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "preview_track_id", - "description": "

Preview track ID

" - }, - { - "group": "Success 200", - "type": "Array[]", - "optional": false, - "field": "genre", - "description": "

An array of genre objects

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "primary_genre_smart", - "description": "

Primary genre smart ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "network_strength", - "description": "

Artist's network strength shown on the dashboard
Possible values in order: active, emerging, established, prominent, influential

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "label", - "description": "

Label ID

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": {\n \"data\": [\n {\n \"id\": 213758,\n \"name\": \"Ashe\",\n \"image_url\": \"https://i.scdn.co/image/ab6761610000e5eb91054f6fce16973e80f0ed2a\",\n \"code2\": \"US\",\n \"verified\": false,\n \"gender\": 0,\n \"band\": false,\n \"genres\": null,\n \"genre_smart_ordered\": [\n {\n \"1\": 501160\n },\n {\n \"2\": 501120\n },\n {\n \"3\": 501132\n },\n {\n \"4\": 503523\n },\n {\n \"5\": 503511\n },\n {\n \"6\": 503513\n },\n {\n \"7\": 419163\n },\n {\n \"8\": 9847\n }\n ],\n \"subgenres\": [\n 419163,\n 9847\n ],\n \"rank\": 2657,\n \"score\": 88.61378455109873,\n \"career_stage\": \"mainstream\",\n \"recent_momentum\": \"steady\",\n \"similarity\": 0.8710287,\n \"sp_followers\": 2438557,\n \"sp_monthly_listeners\": 8878624,\n \"spotify_playlist_total_reach\": 117675156,\n \"sp_playlist_count\": 298873,\n \"youtube_subscribers\": 769000,\n \"youtube_monthly_video_views\": 5704383,\n \"ins_followers\": 1005869,\n \"tiktok_followers\": 1300000,\n \"rank_eg\": 2788,\n \"rank_fb\": 2974,\n \"preview_track_id\": 22431307,\n \"genre\": [\n 501160,\n 501120,\n 501132,\n 503523,\n 503511,\n 503513,\n 419163,\n 9847\n ],\n \"primary_genre_smart\": 501160,\n \"network_strength\": \"prominent\",\n \"label\": 1038765\n },\n {\n \"id\": 481626,\n \"name\": \"Sasha Alex Sloan\",\n \"image_url\": \"https://i.scdn.co/image/ab6761610000e5eb10b7ef8a43b16465acdc0e1d\",\n \"code2\": \"US\",\n \"verified\": true,\n \"gender\": 0,\n \"band\": false,\n \"genres\": null,\n \"genre_smart_ordered\": [\n {\n \"1\": 501120\n },\n {\n \"2\": 503511\n },\n {\n \"3\": 419163\n }\n ],\n \"subgenres\": [\n 419163\n ],\n \"rank\": 2374,\n \"score\": 89.07849757569922,\n \"career_stage\": \"superstar\",\n \"recent_momentum\": \"gradual decline\",\n \"similarity\": 0.87081474,\n \"sp_followers\": 1736437,\n \"sp_monthly_listeners\": 8625576,\n \"spotify_playlist_total_reach\": 118034381,\n \"sp_playlist_count\": 386081,\n \"youtube_subscribers\": 1830000,\n \"youtube_monthly_video_views\": null,\n \"ins_followers\": 706353,\n \"tiktok_followers\": 1100000,\n \"rank_eg\": 2744,\n \"rank_fb\": 2130,\n \"preview_track_id\": 24225427,\n \"genre\": [\n 501120,\n 503511,\n 419163\n ],\n \"primary_genre_smart\": 501120,\n \"network_strength\": \"prominent\",\n \"label\": 5972857\n }\n ],\n \"total\": 10000\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/2762/similar-artists/by-configurations?audience=high&mood=high&genre=high&musicality=high&limit=2&offset=10", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/social-audience-stats", - "title": "Social Audience Stats", - "version": "1.0.0", - "name": "GetSocialAudienceStat", - "description": "

Get Social Audience Analysis and Stats for an artist. Note: We check for new reports daily and update the corresponding audience data only when a new report is present in its API. Most audience reports are updated on a weekly basis, so we should have at least one update per month for this data. However, when using the date parameter, some dates will return empty response structures. An empty response means that the data didn't receive an update for that date. Additionally, while the web app displays audience numbers updated hourly, this endpoint uses cached historical data updated daily to support higher request volumes. This difference in update frequency may lead to small discrepancies in the data.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Artist's Chartmetric ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "instagram", - "youtube", - "tiktok" - ], - "optional": false, - "field": "domain", - "description": "

Social

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "since", - "defaultValue": "today", - "description": "

begining date, in ISO date format (e.g. "2017-03-25"). Default is today.

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

end date, in ISO date format (e.g. "2017-03-25"). Default is today.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "followers", - "likes", - "commenters" - ], - "optional": false, - "field": "audienceType", - "description": "

Specific audience type from source (followers, popularity, listeners, talks, subscribers, etc.)
Each Domain supports different combinations with audienceType. The following combinations are valid:

audienceType instagram youtube tiktok
followers
likes
commenters
" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "country", - "city", - "interest", - "brand", - "language", - "stat", - "demographic" - ], - "optional": false, - "field": "statsType", - "description": "

Specific stat type from source
Each domain supports different combinations with statsType. The following combinations are valid:

statsType instagram youtube tiktok
country
city
interest
brand
language
stat
demographic
" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

the offset of entries to be returned.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "50", - "description": "

The number of entries to be returned

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "followers", - "description": "

Value of metric requested. For YouTube, this is subscribers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "avg_likes_per_post", - "description": "

The value difference between today and 7 days ago for the field.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "avg_comments_per_post", - "description": "

The percentage difference between today and 7 days ago for the field.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "avg_view_per_post", - "description": "

The value difference between today and 28 days ago for the field.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "engagement_rate", - "description": "

The percentage difference between today and 28 days ago for the field.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "brand_id", - "description": "

The brand id.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "brand_name", - "description": "

The brand name.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "scale", - "description": "

The audience affinity.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "brand_followers", - "description": "

Number of followers for the brand.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "weight", - "description": "

The weight of the brand.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

The rank.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "interest_id", - "description": "

The interest id.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "interest_name", - "description": "

The interest name.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "interest_followers", - "description": "

Number of followers for the interest.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "code2", - "description": "

The code2 of the country.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "city_id", - "description": "

The city id.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "city_name", - "description": "

The city's name.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "timestp", - "description": "

time stamp

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"timestp\": \"2021-09-15\",\n \"followers\": 266129565,\n \"avg_likes_per_post\": 4611034,\n \"avg_comments_per_post\": 25014,\n \"avg_views_per_post\": 7397898,\n \"engagement_rate\": 1.742\n },\n {\n \"timestp\": \"2021-09-14\",\n \"followers\": 265850873,\n \"avg_likes_per_post\": 4630634,\n \"avg_comments_per_post\": 25836,\n \"avg_views_per_post\": 7101181,\n \"engagement_rate\": 1.7515\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (Instagram followers brands):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"timestp\": \"2021-09-15\",\n \"brand_id\": 727,\n \"brand_name\": \"Justin Bieber\",\n \"scale\": 1.698967561,\n \"brand_followers\": 5757713,\n \"weight\": 0.021635,\n \"rank\": 5556\n },\n {\n \"timestp\": \"2021-09-15\",\n \"brand_id\": 1147,\n \"brand_name\": \"Samsung Group\",\n \"scale\": 1.329440353,\n \"brand_followers\": 4478162,\n \"weight\": 0.016827,\n \"rank\": 18737\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (Instagram likes interests):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"timestp\": \"2021-09-15\",\n \"interest_id\": 3,\n \"interest_name\": \"Music\",\n \"scale\": 1.213600911,\n \"interest_followers\": 98375592,\n \"weight\": 0.369653,\n \"rank\": 47577\n },\n {\n \"timestp\": \"2021-09-15\",\n \"interest_id\": 80,\n \"interest_name\": \"Beauty & Cosmetics\",\n \"scale\": 1.07163104,\n \"interest_followers\": 54994876,\n \"weight\": 0.206647,\n \"rank\": 37685\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (Instagram likes country):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"timestp\": \"2021-09-15\",\n \"code2\": \"US\",\n \"rank\": 1,\n \"weights\": 0.247117\n },\n {\n \"timestp\": \"2021-09-15\",\n \"code2\": \"BR\",\n \"rank\": 2,\n \"weights\": 0.064284\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (Instagram likes language):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"timestp\": \"2021-09-15\",\n \"language_name\": \"English\",\n \"rank\": 1,\n \"weights\": 0.544543\n },\n {\n \"timestp\": \"2021-09-15\",\n \"language_name\": \"Spanish\",\n \"rank\": 2,\n \"weights\": 0.139369\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (Instagram followers demographic):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"timestp\": \"2021-09-15\",\n \"female\": 0.592988,\n \"male\": 0.407012,\n \"ages_13_17\": 0.10015,\n \"ages_18_24\": 0.459762,\n \"ages_25_34\": 0.34268,\n \"ages_35_44\": 0.075399,\n \"ages_45_64\": 0.022009,\n \"ages_65_\": 0,\n \"female_13_17\": 0.072495,\n \"female_ages_18_24\": 0.299856,\n \"female_ages_25_34\": 0.183444,\n \"female_ages_35_44\": 0.028788,\n \"female_ages_45_64\": 0.008405,\n \"female_ages_65_\": 0,\n \"male_13_17\": 0.027655,\n \"male_ages_18_24\": 0.159906,\n \"male_ages_25_34\": 0.159236,\n \"male_ages_35_44\": 0.046611,\n \"male_ages_45_64\": 0.013604,\n \"male_ages_65_\": 0\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Instagram followers stats:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/3963/social-audience-stats?domain=instagram&audienceType=followers&statsType=stat&since=2021-09-13", - "type": "curl" - }, - { - "title": "Instagram followers brands stats:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/3963/social-audience-stats?domain=instagram&audienceType=followers&statsType=brand&since=2021-09-13", - "type": "curl" - }, - { - "title": "Instagram likes interests stats:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/3963/social-audience-stats?domain=instagram&audienceType=likes&statsType=interest&since=2021-09-13", - "type": "curl" - }, - { - "title": "Instagram likes country stats:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/3963/social-audience-stats?domain=instagram&audienceType=likes&statsType=country&since=2021-09-13", - "type": "curl" - }, - { - "title": "Instagram likes language stats:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/3963/social-audience-stats?domain=instagram&audienceType=likes&statsType=language&since=2021-09-13", - "type": "curl" - }, - { - "title": "Instagram followers demographic stats:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/3963/social-audience-stats?domain=instagram&audienceType=followers&statsType=demographic&since=2021-09-13", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:artistId/venues", - "title": "Venues", - "version": "1.0.0", - "name": "GetVenuesAndEventsByArtist", - "description": "

Get venues and their events by artist ID

", - "group": "Artist", - "parameter": { - "fields": { - "Path Parameters": [ - { - "group": "Path Parameters", - "type": "Number", - "optional": false, - "field": "artistId", - "description": "

Artist ID.

" - } - ], - "Query Parameters": [ - { - "group": "Query Parameters", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "20", - "description": "

Limit of number of matched events. Max value: 100

" - }, - { - "group": "Query Parameters", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "obj", - "description": "" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "obj.venue_id", - "description": "

Venue ID

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "obj.venue_name", - "description": "

Venue name

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "obj.venue_url", - "description": "

URL of the venue

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "obj.venue_capacity", - "description": "

Capacity of the venue

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "obj.city", - "description": "

City object

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.city.id", - "description": "

City ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.city.name", - "description": "

City name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.code2", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "obj.events", - "description": "

List of events at the venue

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.events.id", - "description": "

Event ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.events.event_name", - "description": "

Name of the event

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.events.jambase_event_url", - "description": "

URL of the event

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.events.songkick_event_url", - "description": "

URL of the event on Songkick

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.events.seatgeek_event_url", - "description": "

URL of the event on SeatGeek

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.events.type", - "description": "

Type of the event (e.g., Concert, Festival)

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "obj.events.start_date", - "description": "

Start date of the event

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "obj.events.end_date", - "description": "

End date of the event

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "obj.events.is_headliner", - "description": "

Whether the artist is a headliner

" - } - ] - }, - "examples": [ - { - "title": "Example usage", - "content": "{\n \"obj\": [\n {\n \"venue_id\": 434,\n \"venue_name\": \"Dodger Stadium\",\n \"venue_url\": \"https://www.jambase.com/venue/dodger-stadium\",\n \"venue_capacity\": 56000,\n \"city\": {\n \"id\": 7058,\n \"name\": \"Los Angeles\"\n },\n \"code2\": \"US\",\n \"events\": [\n {\n \"id\": 356,\n \"event_name\": \"Besame Mucho (cancelled)\",\n \"jambase_event_url\": \"https://www.jambase.com/festival/besame-mucho-2024\",\n \"songkick_event_url\": \"https://www.songkick.com/festivals/2317029-besame-mucho/id/41963404-besame-mucho-2024?utm_source=46722&utm_medium=partner\",\n \"seatgeek_event_url\": null,\n \"type\": \"Festival\",\n \"start_date\": \"2024-12-21 00:00:00.000\",\n \"end_date\": \"2024-12-21 00:00:00.000\",\n \"is_headliner\": true\n }\n ]\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage", - "content": "GET curl -H 'Authorization: Bearer ACCESS_KEY' https://api.chartmetric.com/api/artist/8433383/venues?limit=5&offset=0", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/where-people-listen", - "title": "Spotify Monthly Listeners by City", - "version": "1.0.0", - "name": "GetWherePeopleListenInsights", - "description": "

Spotify's "Where people listen" stats, showing cities on each day.

Note: Spotify data for top 50 cities available prior to Aug 12, 2024, and we provide estimates after that date. For estimates, data updates are not daily.

As of 03/17/2025, this endpoint is limited to 10 requests per-second per-user to maintain system stability.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "since", - "description": "

Beginning date in ISO date format (e.g. "2017-03-25"). This parameter is required.

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

End date in ISO date format (e.g. "2017-03-25").

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "description": "

Date in ISO date format (e.g. "2017-03-25"). This parameter can be passed in instead of since and until, to fetch a snapshot for a specific date. If this is true, since/until parameters will be ignored.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of locations to be returned. This is applied to cities and countries separately. (default 10, max 50).

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of locations to be returned. This is applied to cities and countries separately.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date. If this is true, since/until parameters will be ignored.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "includeEstimates", - "defaultValue": "true", - "description": "

If set to true, includes estimates for cities and countries.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "cities", - "description": "

List of cities

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "cities.timestp", - "description": "

time stamp

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cities.code2", - "description": "

country code

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cities.spotify_artist_id", - "description": "

Spotify artist ID

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cities.lat", - "description": "

Latitude of the city

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cities.lng", - "description": "

Longitude of the city

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cities.region", - "description": "

region

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cities.city_id", - "description": "

unique city ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cities.spotify_artist_insights_location", - "description": "

Spotify artist insights location (null if not available)

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cities.current_max_count", - "description": "

current maximum count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cities.market_max_cm_artist_id", - "description": "

Chartmetric artist ID for the artist with the maximum listeners in the market

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cities.market_max_artist_name", - "description": "

Name of the artist with the maximum listeners in the market

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cities.listeners", - "description": "

current listeners count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cities.prev_listeners", - "description": "

previous listeners count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cities.artist_city_rank", - "description": "

artist rank in the city (null if not available)

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cities.max_listeners", - "description": "

maximum listeners count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cities.population", - "description": "

city population

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cities.city_affinity", - "description": "

affinity to the city (null if not available)

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "cities.is_estimate", - "description": "

whether the data is an estimate

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "countries", - "description": "

List of countries

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "countries.timestp", - "description": "

time stamp

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "countries.code2", - "description": "

country code

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "countries.spotify_artist_id", - "description": "

Spotify artist ID

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "countries.lat", - "description": "

Latitude of the country

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "countries.lng", - "description": "

Longitude of the country

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "countries.region", - "description": "

region

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "countries.market_max_cm_artist_id", - "description": "

Chartmetric artist ID for the artist with the maximum listeners in the country (null if not applicable)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "countries.market_max_artist_name", - "description": "

Name of the artist with the maximum listeners in the country (null if not applicable)

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "countries.listeners", - "description": "

current listeners count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "countries.prev_listeners", - "description": "

previous listeners count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "countries.artist_city_rank", - "description": "

artist rank in the country (null if not available)

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "countries.max_listeners", - "description": "

maximum listeners count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "countries.population", - "description": "

country population

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "countries.is_estimate", - "description": "

whether the data is an estimate

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"cities\": {\n \"Brisbane\": [\n {\n \"timestp\": \"2024-10-15T07:00:00.000Z\",\n \"code2\": \"AU\",\n \"spotify_artist_id\": \"06HL4z0CvFAxyc27GXpf02\",\n \"lat\": -27.455,\n \"lng\": 153.0351,\n \"region\": \"CITY\",\n \"city_id\": 591,\n \"spotify_artist_insights_location\": null,\n \"current_max_count\": 616040,\n \"market_max_cm_artist_id\": 212715,\n \"market_max_artist_name\": \"Morgan Wallen\",\n \"listeners\": 625990,\n \"prev_listeners\": 621474,\n \"artist_city_rank\": null,\n \"max_listeners\": 92958654,\n \"population\": 2360241,\n \"city_affinity\": null,\n \"is_estimate\": true\n },\n ...\n ],\n ...\n },\n \"countries\": {\n \"Australia\": [\n {\n \"timestp\": \"2024-10-15T07:00:00.000Z\",\n \"code2\": \"AU\",\n \"spotify_artist_id\": \"06HL4z0CvFAxyc27GXpf02\",\n \"lat\": -25.274398,\n \"lng\": 133.775136,\n \"region\": \"COUNTRY\",\n \"city_id\": 591,\n \"spotify_artist_insights_location\": null,\n \"current_max_count\": 616040,\n \"market_max_cm_artist_id\": null,\n \"market_max_artist_name\": null,\n \"listeners\": 5497904,\n \"prev_listeners\": 5508062,\n \"artist_city_rank\": null,\n \"max_listeners\": 92958654,\n \"population\": 26033500,\n \"city_affinity\": null,\n \"is_estimate\": true\n },\n ...\n ],\n ...\n }\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/2762/where-people-listen?latest=true", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:id/:type/charts", - "title": "Charts", - "version": "1.0.0", - "name": "getArtistCharts", - "description": "

This API returns the charts for this artist. For example, you can retrieve chart records from Spotify Charts, iTunes Charts, Apple Music Charts, Shazam Charts or Beatport Charts.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify_viral_daily", - "spotify_viral_weekly", - "spotify_top_daily", - "spotify_top_weekly", - "applemusic_top", - "applemusic_daily", - "applemusic_albums", - "itunes_top", - "itunes_albums", - "shazam", - "beatport", - "youtube", - "youtube_tracks", - "youtube_videos", - "youtube_trends", - "amazon" - ], - "optional": false, - "field": "type", - "description": "" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Artist Chartmetric ID

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "since", - "description": "

Start date in ISO date format (e.g. "2020-03-01"). This parameter is required and should be within 365 days from current date.

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

End date in ISO date format (e.g. "2020-09-01"). This should not exceed more than 365 days from since date.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "daily", - "weekly", - "all_time" - ], - "optional": false, - "field": "duration", - "description": "

If the chart has it, you can pass in the duration of the chart.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "data", - "description": "" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "entries", - "description": "

Array of objects that contain chart data

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entries.spotify_id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entries.rank", - "description": "

Chart Position

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "entries.added_at", - "description": "

Date of Data Recorded

" - }, - { - "group": "Success 200", - "type": "String", - "allowedValues": [ - "US", - "JP", - "..." - ], - "optional": false, - "field": "entries.code2", - "description": "

2-Letter Country Code. (Full List in Introduction).

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entries.plays", - "description": "

Number of Plays

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "entries.duration", - "description": "

How long it was on the chart for

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "entries.chart_type", - "description": "

Chart type

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "entries.chart_name", - "description": "

Chart name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "entries.gnp", - "description": "" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entries.pre_rank", - "description": "

Latest Previous

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entries.peak_rank", - "description": "

Best Rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "entries.peak_date", - "description": "

Date of Best

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entries.peak_plays", - "description": "

Best Plays

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "entries.total_plays", - "description": "

Total Plays

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entries.id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "entries.name", - "description": "

Name of track

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "entries.isrc", - "description": "

ISRC of track

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "entries.spotify_album_id", - "description": "

Album the track was in

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "entries.image_url", - "description": "

Image

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entries.spotify_popularity", - "description": "

Spotify popularity stat

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entries.cm_track", - "description": "

CM Track ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "entries.spotify_artist_names", - "description": "

List of related artist names

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "entries.track_genre", - "description": "

All genres for that track

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "entries.cm_artist", - "description": "

CM Artist ids of all artists related

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "entries.artist_names", - "description": "

All related artist names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "entries.code2s", - "description": "

All related country codes

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "entries.artist_images", - "description": "

All related artist images

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "entries.artist_covers", - "description": "

All related covers

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "entries.spotify_artist_ids", - "description": "

All Spotify artist IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "entries.spotify_track_ids", - "description": "

All Spotify track IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "entries.spotify_album_ids", - "description": "

All Spotify Album IDs

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entries.spotify_duration_ms", - "description": "

Spotify duration in miliseconds

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "entries.album_names", - "description": "

Album Names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "entries.album_upc", - "description": "

Album UPCs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "entries.album_label", - "description": "

Album Labels

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "entries.release_dates", - "description": "

Release Dates of the albums

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "uniqueTracks", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "uniqueTracks.cm_track", - "description": "

CM Track ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "uniqueTracks.cm_album", - "description": "

CM Album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "uniqueTracks.name", - "description": "

Name of track

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "uniqueTracks.image_url", - "description": "

Image of track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "uniqueTracks.chart_events", - "description": "

Chart event

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "uniqueTracks.total_plays", - "description": "

Total plays

" - } - ] - }, - "examples": [ - { - "title": "Success-Response", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"data\": {\n \"entries\": [\n {\n \"spotify_id\": 14455213,\n \"rank\": 111,\n \"added_at\": \"2020-03-03T08:00:00.000Z\",\n \"code2\": \"AU\",\n \"plays\": 40234,\n \"duration\": \"daily\",\n \"chart_type\": \"regional\",\n \"chart_name\": \"DAILY\",\n \"gnp\": \"351182.00\",\n \"pre_rank\": 117,\n \"peak_rank\": 73,\n \"peak_date\": \"2019-12-05T08:00:00.000Z\",\n \"peak_plays\": 58816,\n \"total_plays\": \"7805075\",\n \"id\": 14455213,\n \"name\": \"Woke Up Late (feat. Hailee Steinfeld)\",\n \"isrc\": \"QMCE31801829\",\n \"spotify_album_id\": \"5gdsguQxwQQlpTi1SU3tbY\",\n \"image_url\": \"https://i.scdn.co/image/5b41143718f3e76ddf0bf10600ca511102a0a105\",\n \"spotify_popularity\": 62,\n \"cm_track\": 22310714,\n \"spotify_artist_names\": [\n \"Drax Project\",\n \"Hailee Steinfeld\"\n ],\n \"track_genre\": \"dance pop,pop,post-teen pop,australian pop,nz pop\",\n \"cm_artist\": [\n 210051,\n 4904\n ],\n \"artist_names\": [\n \"Drax Project\",\n \"Hailee Steinfeld\"\n ],\n \"code2s\": [\n \"NZ\",\n \"US\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/387630502c83e7f465858650588732f53e0e3af1\",\n \"https://i.scdn.co/image/88d4d742b9826c61ac60e0a4eca865994fc04c70\"\n ],\n \"artist_covers\": [\n null,\n null\n ],\n \"spotify_artist_ids\": [\n \"6S0IvKlvPMX1RtAYtVpUV8\",\n \"5p7f24Rk5HkUZsaS3BLG5F\"\n ],\n \"spotify_track_ids\": [\n \"4oPWkP9zQ5ANBbfdHGOvKF\"\n ],\n \"spotify_album_ids\": [\n \"5gdsguQxwQQlpTi1SU3tbY\"\n ],\n \"spotify_duration_ms\": 181826,\n \"album_ids\": [\n 4883983\n ],\n \"album_names\": [\n \"Drax Project\"\n ],\n \"album_upc\": [\n \"814908028765\"\n ],\n \"album_label\": [\n \"300 Entertainment\"\n ],\n \"release_dates\": [\n \"2019-09-27T07:00:00.000Z\"\n ]\n },\n {\n \"spotify_id\": 14455213,\n \"rank\": 104,\n \"added_at\": \"2020-03-03T08:00:00.000Z\",\n \"code2\": \"NZ\",\n \"plays\": 8405,\n \"duration\": \"daily\",\n \"chart_type\": \"regional\",\n \"chart_name\": \"DAILY\",\n \"gnp\": \"54669.00\",\n \"pre_rank\": 110,\n \"peak_rank\": 89,\n \"peak_date\": \"2020-01-25T08:00:00.000Z\",\n \"peak_plays\": 10134,\n \"total_plays\": \"1175303\",\n \"id\": 14455213,\n \"name\": \"Woke Up Late (feat. Hailee Steinfeld)\",\n \"isrc\": \"QMCE31801829\",\n \"spotify_album_id\": \"5gdsguQxwQQlpTi1SU3tbY\",\n \"image_url\": \"https://i.scdn.co/image/5b41143718f3e76ddf0bf10600ca511102a0a105\",\n \"spotify_popularity\": 62,\n \"cm_track\": 22310714,\n \"spotify_artist_names\": [\n \"Drax Project\",\n \"Hailee Steinfeld\"\n ],\n \"track_genre\": \"dance pop,pop,post-teen pop,australian pop,nz pop\",\n \"cm_artist\": [\n 210051,\n 4904\n ],\n \"artist_names\": [\n \"Drax Project\",\n \"Hailee Steinfeld\"\n ],\n \"code2s\": [\n \"NZ\",\n \"US\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/387630502c83e7f465858650588732f53e0e3af1\",\n \"https://i.scdn.co/image/88d4d742b9826c61ac60e0a4eca865994fc04c70\"\n ],\n \"artist_covers\": [\n null,\n null\n ],\n \"spotify_artist_ids\": [\n \"6S0IvKlvPMX1RtAYtVpUV8\",\n \"5p7f24Rk5HkUZsaS3BLG5F\"\n ],\n \"spotify_track_ids\": [\n \"4oPWkP9zQ5ANBbfdHGOvKF\"\n ],\n \"spotify_album_ids\": [\n \"5gdsguQxwQQlpTi1SU3tbY\"\n ],\n \"spotify_duration_ms\": 181826,\n \"album_ids\": [\n 4883983\n ],\n \"album_names\": [\n \"Drax Project\"\n ],\n \"album_upc\": [\n \"814908028765\"\n ],\n \"album_label\": [\n \"300 Entertainment\"\n ],\n \"release_dates\": [\n \"2019-09-27T07:00:00.000Z\"\n ]\n }\n ],\n \"uniqueTracks\": [\n {\n \"cm_track\": \"22310714\",\n \"cm_album\": null,\n \"name\": \"Woke Up Late (feat. Hailee Steinfeld)\",\n \"image_url\": \"https://i.scdn.co/image/5b41143718f3e76ddf0bf10600ca511102a0a105\",\n \"chart_events\": 2,\n \"total_plays\": 8980378\n }\n ]\n }\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/4904/spotify_top_daily/charts?since=2020-03-01&until=2020-03-04", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:type/:id/get-ids", - "title": "Get Artist IDs", - "version": "1.0.0", - "name": "getArtistIDs", - "description": "

This API returns all the linked IDs. For example, you can retrieve Chartmetric ID from Spotify, iTunes (Apple Music), Deezer, Amazon, YouTube, and vice versa.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "chartmetric", - "spotify", - "itunes", - "deezer", - "amazon", - "youtube" - ], - "optional": false, - "field": "type", - "description": "

Platform.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "id", - "description": "

Artist ID. Corresponds to the platform specified by :type. Note that type=youtube expects the channel ID, not the artist ID.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10000", - "description": "

The number of entries to be returned, maximum of 10000. This parameter affects only aggregate=false requests. aggregate=true requests returns a single result with all of the IDs.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned. This parameter affects only aggregate=false requests.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "aggregate", - "defaultValue": "false", - "description": "

Setting aggregate=true returns platform IDs aggregated into arrays.
See Success-Response (aggregate=true) below.
Note: aggregate is deprecated and will be removed after June 30, 2024. Response will always be aggregated.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_artist", - "description": "

Chartmetric artist ID. If aggregate=false.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artist_name", - "description": "

Artist name. If aggregate=false.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "spotify_artist_id", - "description": "

Spotify Artist ID. If aggregate=false.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "itunes_artist_id", - "description": "

iTunes Artist ID. If aggregate=false.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "deezer_artist_id", - "description": "

Deezer Artist ID. If aggregate=false.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "amazon_artist_id", - "description": "

Amazon Artist ID. If aggregate=false.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "youtube_channel_id", - "description": "

YouTube channel ID. If aggregate=false.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "chartmetric_id", - "description": "

Chartmetric Artist IDs. If aggregate=true.

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "spotify_artist_ids", - "description": "

Spotify Artist IDs. If aggregate=true.

" - }, - { - "group": "Success 200", - "type": "Number[]", - "optional": false, - "field": "itunes_artist_ids", - "description": "

iTunes Artist IDs. If aggregate=true.

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "deezer_artist_ids", - "description": "

Deezer Artist IDs. If aggregate=true.

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "amazon_artist_ids", - "description": "

Amazon Artist IDs. If aggregate=true.

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "youtube_channel_ids", - "description": "

YouTube channel IDs. If aggregate=true.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "instagram_id", - "description": "

Instagram ID. If aggregate=true.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response (aggregate=true):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"chartmetric_id\": 4904,\n \"artist_name\": \"Hailee Steinfeld\",\n \"spotify_artist_ids\": [\n \"5p7f24Rk5HkUZsaS3BLG5F\"\n ],\n \"itunes_artist_ids\": [\n 417571723\n ],\n \"deezer_artist_ids\": [\n \"5961630\"\n ],\n \"amazon_artist_ids\": [\n \"B00L4I14C0\",\n \"B00L48A8F4\",\n \"B00L3SD3CU\",\n \"B00L3RW8OA\",\n \"B00L4HBC14\",\n \"B00L3PD4MW\",\n \"B00L499IG8\"\n ],\n \"youtube_channel_ids\": [\n \"UCWfytcGFwPSMwvP5HYuXGqw\"\n ],\n \"instagram_id\": 2274763833\n }\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (default, aggregate=false):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"cm_artist\": 4904,\n \"artist_name\": \"Hailee Steinfeld\",\n \"spotify_artist_id\": \"5p7f24Rk5HkUZsaS3BLG5F\",\n \"itunes_artist_id\": 417571723,\n \"deezer_artist_id\": \"5961630\",\n \"amazon_artist_id\": \"B00L4I14C0\",\n \"youtube_channel_id\": \"UCWfytcGFwPSMwvP5HYuXGqw\",\n },\n {\n \"cm_artist\": 4904,\n \"artist_name\": \"Hailee Steinfeld\",\n \"spotify_artist_id\": \"5p7f24Rk5HkUZsaS3BLG5F\",\n \"itunes_artist_id\": 417571723,\n \"deezer_artist_id\": \"5961630\",\n \"amazon_artist_id\": \"B00L48A8F4\",\n \"youtube_channel_id\": \"UCWfytcGFwPSMwvP5HYuXGqw\",\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage (aggregate=true):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/chartmetric/4904/get-ids?aggregate=true", - "type": "curl" - }, - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/chartmetric/4904/get-ids", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/list/filter", - "title": "Get Artists (with filters)", - "version": "1.0.0", - "name": "getArtistListByFilters", - "description": "

Get an artists snapshot of sample artists based on a given filters.

Given different filters and score range, get a list of artist ids and all relevant metrics for the given range of the respective metric.

This endpoint is useful for artist profiling based on specific filters such as Spotify performance, YouTube performance and social media performance.

Sorting

Sorting is done by passing any valid combination of <period>.<stat> from the table above to the parameter sortColumn. For example, given the stat airplay_streams, it's possible to sort by any of latest.airplay_streams, weekly_diff.airplay_streams, and monthly_diff.airplay_streams. For every artist, the following periods and stats are usable for sorting:

Period Explanation
latest The latest available value
weekly_diff Change in the last 7 days
monthly_diff Change in the last 28 days
weekly_diff_percent Change in the last 7 days in percentage
monthly_diff_percent Change in the last 28 days in percentage
Stat (scrollable)
amazon_ed_playlist_count
amazon_playlist_count
boomplay_comments
boomplay_favorites
boomplay_plays
boomplay_ranking_current
boomplay_shares
bs_followers
cm_artist_rank
deezer_ed_playlist_count
deezer_ed_playlist_total_reach
deezer_fans
deezer_playlist_count
deezer_playlist_total_reach
facebook_followers
fs_likes
fs_talks
genius_pageviews
ins_engagement_rate
ins_followers
itunes_ed_playlist_count
itunes_playlist_count
line_music_artist_likes
line_music_likes
line_music_mv_plays
line_music_plays
melon_artist_fans
melon_likes
melon_video_likes
melon_video_views
pandora_lifetime_stations_added
pandora_lifetime_streams
pandora_listeners_28_day
rank_eg
shazam_count
soundcloud_followers
soundcloud_plays
sp_followers
sp_followers_to_listeners_ratio
sp_listeners_to_followers_ratio
sp_monthly_listeners
sp_popularity
spotify_ed_playlist_count
spotify_ed_playlist_total_reach
spotify_playlist_count
spotify_playlist_total_reach
tiktok_followers
tiktok_likes
tiktok_top_video_views
tiktok_track_posts
ts_followers
twitch_followers
twitch_monthly_viewer_hours
twitch_views
twitch_weekly_viewer_hours
ws_views
ycs_subscribers
ycs_views
youtube_daily_video_views
youtube_ed_playlist_count
youtube_ed_playlist_total_reach
youtube_monthly_video_views
youtube_playlist_count
youtube_playlist_total_reach
rank_fb

This association denotes the period of a given stat. For example, the stat weekly_diff.sp_followers refers to the change in Spotify follower counts during the last 7 days for a given artist.

Only 10,000 results may be returned from any result set on this route (limit + offset <= 10,000). This limitation is not new, but it's now being documented.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "tagId", - "description": "

Filter by Genre Id.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "subTagId", - "description": "

Filter by Subgenre Id.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "code2", - "description": "

Filtering by artist's country.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "firstReleaseDaysAgo", - "description": "

Filter base on artists first release based on number of days ago from current date.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "band", - "defaultValue": "false", - "description": "

Filtering bands or solo artist only.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "any", - "he/him", - "notSpecified", - "others", - "she/her", - "they/them" - ], - "optional": true, - "field": "pronoun", - "description": "

Filtering by artist's pronoun.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "[period].[stat]", - "[stat]" - ], - "optional": true, - "field": "sortColumn", - "defaultValue": "latest.cm_artist_rank", - "description": "

The sorting order of the artist list base on filters.

[stat] will resort to latest.[stat] if no period is provided.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "sortOrderDesc", - "defaultValue": "true", - "description": "

Whether to sort in descending order.

" - }, - { - "group": "Parameter", - "type": "Integer[]", - "optional": true, - "field": "eventIds", - "description": "

An array of event IDs, such as festivals, to filter artists by.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "sp_p", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Spotify popularity.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "sp_f", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Spotify followers.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "dz_fans", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Deezer fans.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "sp_ml", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Spotify monthly listeners.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "sp_ratio", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Spotify listeners/fans ratio.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "sp_fl_ratio", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Spotify fans/listeners ratio.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "tt_f", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for TikTok followers.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "tt_l", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for TikTok likes.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "fb_f", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for facebook followers.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "fb_l", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for facebook likes.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "fb_t", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for facebook talks.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "tw_f", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for twitter followers.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "ig_f", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Instagram popularity.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "ytc_v", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for YouTube channel views.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "ytc_s", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for YouTube channel subscribers.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "ytd_vv", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for YouTube daily video views.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "ytm_vv", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for YouTube monthly video views.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "wp_v", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Wikipedia views.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "sc_f", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Soundcloud followers.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "bit_f", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Bandsintown followers.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "cpp", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for CPP (Ranking).

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "t_f", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Twitch followers.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "t_v", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Twitch views.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "t_mvh", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Twitch monthly viewer hours.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "t_wvh", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Twitch weekly viewr hours.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "p_ml", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Pandora monthly listeners.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "p_ls", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Pandora lifetime stations.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "p_lsr", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for Pandora lifetime streams.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "bp_c", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for boomplay comments.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "bp_f", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for boomplay favorites.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "bp_p", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for boomplay plays.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "bp_s", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for boomplay shares.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "undiscovered", - "developing", - "mid-level", - "mainstream", - "superstar", - "legendary" - ], - "optional": true, - "field": "career_stage", - "description": "

Filter by the artist's career stage.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "career_stage_score", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for career stage score.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "decline", - "gradual decline", - "steady", - "growth", - "explosive growth" - ], - "optional": true, - "field": "career_trend", - "description": "

Filter by the artist's career trend category.

" - }, - { - "group": "Parameter", - "type": "Array", - "optional": true, - "field": "career_trend_score", - "description": "

A 2-item integer array consist of maximum and minium filter threshold for career trend score.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "50", - "description": "

The number of entries to be returned. Maximum value 100. Note: We do allow a limit of a maximum value of limit=200 for compatibility with legacy behavior. This greater limit is subject to change at any time. Prefer the maximum limit=100 to avoid breaking changes.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Number[]", - "optional": true, - "field": "shortlistIds", - "description": "

Array of shortlist IDs this item is included on

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "obj", - "description": "

List of results

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.cm_artist", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.pronoun", - "description": "

Artist's pronoun

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "obj.band", - "description": "

true if the artist profile is for a band

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.image_url", - "description": "

Artist image URL

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.code2", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.hometown_city", - "description": "

Hometown city

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.current_city", - "description": "

Current city

" - }, - { - "group": "Success 200", - "type": "Number[]", - "optional": false, - "field": "obj.cm_tag", - "description": "

List of genre IDs

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.genres", - "description": "

Genre names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "obj.genre_smart_ordered", - "description": "

An array of genre objects where the key is the order of the genre and the value is the new genre id.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.cm_artist_rank", - "description": "

Chartmetric's artist rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.rank_fb", - "description": "

Fanbase rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.rank_eg", - "description": "

Engagement rank

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.sp_followers", - "description": "

Number of Spotify followers

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.deezer_fans", - "description": "

Number of Deezer followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.sp_popularity", - "description": "

Popularity on Spotify

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "obj.sp_followers_to_listeners_ratio", - "description": "

The ratio of Spotify followers and monthly listeners

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.sp_monthly_listeners", - "description": "

Number of monthly listeners on Spotify

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.bs_followers", - "description": "

Number of BandsInTown followers

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.boomplay_plays", - "description": "

Number of Boomplay plays

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.boomplay_favorites", - "description": "

Number of times favorited in Boomplay

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.ws_views", - "description": "

Number of Wikipedia views

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.soundcloud_followers", - "description": "

Number of SoundCloud followers

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.soundcloud_plays", - "description": "

Number of SoundCloud plays

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.ins_followers", - "description": "

Number of Instagram followers

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.ts_followers", - "description": "

Number of Twitter followers

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.facebook_followers", - "description": "

Number of Facebook followers

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.ycs_subscribers", - "description": "

Number of artist's YouTube channel subscribers

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.ycs_views", - "description": "

Number of artists' YouTube channel views

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.tiktok_followers", - "description": "

Number of TikTok followers

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.tiktok_likes", - "description": "

Number of TikTok likes

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.tiktok_top_video_views", - "description": "

Sum of views of the top TikTok videos featuring this artist's music

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.tiktok_track_posts", - "description": "

Number of top TikTok videos featuring this artist's music

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.line_music_artist_likes", - "description": "

Number of artist's Line Music likes

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.line_music_likes", - "description": "

Number of Line Music likes for tracks featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.line_music_plays", - "description": "

Number of Line Music plays

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.line_music_mv_plays", - "description": "

Number of Line Music music video plays

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.melon_artist_fans", - "description": "

Number of Melon fans

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.melon_likes", - "description": "

Number of Melon likes

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.melon_video_likes", - "description": "

Number of Melon video likes

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.melon_video_views", - "description": "

Number of Melon views

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.youtube_daily_video_views", - "description": "

Number of daily YouTube video views

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.youtube_monthly_video_views", - "description": "

Number of monthly YouTube video views

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.twitch_followers", - "description": "

Number of Twitch followers

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.twitch_views", - "description": "

Number of Twitch views

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.twitch_monthly_viewer_hours", - "description": "

Number of monthly Twitch viewer hours

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.twitch_weekly_viewer_hours", - "description": "

Number of weekly Twitch viewer hours

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.pandora_listeners_28_day", - "description": "

Number of Pandora listeners in the last 28 days

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.pandora_lifetime_stations_added", - "description": "

Number of Pandora stations (all time)

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.pandora_lifetime_streams", - "description": "

Number of Pandora streams (all time)

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.num_sp_editorial_playlists", - "description": "

Number of Spotify editorial playlists featuring this artist.

Note: This field was renamed to spotify_ed_playlist_count. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.spotify_ed_playlist_count", - "description": "

Number of Spotify editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.num_sp_playlists", - "description": "

Number of Spotify playlists featuring this artist.

Note: This field was renamed to spotify_playlist_count. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.spotify_playlist_count", - "description": "

Number of Spotify playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.sp_playlist_total_reach", - "description": "

Sum of followers from all Spotify playlists, indicating total playlists reach.

Note: This field was renamed to spotify_playlist_total_reach. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.spotify_playlist_total_reach", - "description": "

Sum of followers from all Spotify playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.sp_editorial_playlist_total_reach", - "description": "

Number of followers from Spotify editorial playlists.

Note: This field was renamed to spotify_ed_playlist_total_reach. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.spotify_ed_playlist_total_reach", - "description": "

Number of followers from Spotify editorial playlists

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.num_am_editorial_playlists", - "description": "

Number of editorial Apple Music playlists featuring this artist.

Note: This field was renamed to itunes_ed_playlist_count. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.itunes_ed_playlist_count", - "description": "

Number of editorial Apple Music playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.num_am_playlists", - "description": "

Number of Apple Music playlists featuring this artist.

Note: This field was renamed to itunes_playlist_count. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.itunes_playlist_count", - "description": "

Number of Apple Music playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.num_de_editorial_playlists", - "description": "

Number of editorial Deezer playlists featuring this artist.

Note: This field was renamed to deezer_ed_playlist_count. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.deezer_ed_playlist_count", - "description": "

Number of editorial Deezer playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.num_de_playlists", - "description": "

Number of Deezer playlists featuring this artist.

Note: This field was renamed to deezer_playlist_count. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.deezer_playlist_count", - "description": "

Number of Deezer playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.de_playlist_total_reach", - "description": "

Sum of fans from all Deezer playlists, indicating total playlists reach.

Note: This field was renamed to deezer_playlist_total_reach. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.deezer_playlist_total_reach", - "description": "

Sum of fans from all Deezer playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.de_editorial_playlist_total_reach", - "description": "

Number of fans from editorial Deezer playlists.

Note: This field was renamed to deezer_ed_playlist_total_reach. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.deezer_ed_playlist_total_reach", - "description": "

Number of fans from editorial Deezer playlists

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.num_az_editorial_playlists", - "description": "

Number of Amazon editorial playlists featuring this artist.

Note: This field was renamed to amazon_ed_playlist_count. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.amazon_ed_playlist_count", - "description": "

Number of Amazon editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.num_az_playlists", - "description": "

Number of Amazon playlists featuring this artist.

Note: This field was renamed to amazon_playlist_count. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.amazon_playlist_count", - "description": "

Number of Amazon playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.num_yt_editorial_playlists", - "description": "

Number of YouTube editorial playlists featuring this artist.

Note: This field was renamed to youtube_ed_playlist_count. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.youtube_ed_playlist_count", - "description": "

Number of YouTube editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.num_yt_playlists", - "description": "

Number of YouTube playlists featuring this artist.

Note: This field was renamed to youtube_playlist_count. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.youtube_playlist_count", - "description": "

Number of YouTube playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.yt_playlist_total_reach", - "description": "

Sum of views across YouTube playlists featuring this artist.

Note: This field was renamed to youtube_playlist_total_reach. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.youtube_playlist_total_reach", - "description": "

Sum of views across YouTube playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.yt_editorial_playlist_total_reach", - "description": "

Sum of views across editorial YouTube playlists featuring this artist.

Note: This field was renamed to youtube_ed_playlist_total_reach. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.youtube_ed_playlist_total_reach", - "description": "

Sum of views across editorial YouTube playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.facebook_likes", - "description": "

Number of Facebook likes for this artist.

Note: This field was renamed to fs_likes. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.fs_likes", - "description": "

Number of Facebook likes for this artist

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.facebook_talks", - "description": "

Number of people talking about the artist's page on Facebook.

Note: This field was renamed to fs_talks. It is deprecated and it will be removed after Dec 31, 2023.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.fs_talks", - "description": "

Number of people talking about the artist's page on Facebook

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.shazam_count", - "description": "

Number of Shazam plays

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.genius_pageviews", - "description": "

Number of Genius page views

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "obj.career_status", - "description": "

Artist's career stage and trend information

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.career_status.stage", - "description": "

Artist's current career stage as shown on the dashboard.

Possible values in order: undiscovered, developing, mid-level, mainstream, superstar, legendary

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.career_status.stage_score", - "description": "

Value from 0 to 100 representing Artist's relative progression through their current stage.

For example, a score of 100 in the undiscovered stage is directly adjacent to a score of 0 in the developing stage.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.career_status.trend", - "description": "

Artist's current career trend category as shown on the dashboard.

Possible values in order: decline, gradual decline, steady, growth, explosive growth.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.career_status.trend_score", - "description": "

Value from 0 to 100 representing the relative strength of the Artist's trend within their trend category.

For example, a score of 100 in the steady category is directly adjacent to a score of 0 in the growth category.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "offset", - "description": "

The offset of entries to be returned

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "total", - "description": "

Total number of entries

" - } - ] - }, - "examples": [ - { - "title": "Success-Response", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"obj\": [\n {\n \"cm_artist\": \"3852\",\n \"name\": \"The Weeknd\",\n \"pronoun\": \"he/him\",\n \"band\": false,\n \"image_url\": \"https://i.scdn.co/image/ab6761610000e5eb214f3cf1cbe7139c1e26ffbb\",\n \"code2\": \"CA\",\n \"cm_tag\": [\n 462882,\n 462889,\n 462887\n ],\n \"career_status\": {\n \"stage\": \"superstar\",\n \"stage_score\": 81,\n \"trend\": \"steady\",\n \"trend_score\": 48\n },\n \"cm_artist_rank\": 3,\n \"rank_eg\": 4,\n \"rank_fb\": 8,\n \"amazon_ed_playlist_count\": 953,\n \"amazon_playlist_count\": 1022,\n \"boomplay_comments\": null,\n \"boomplay_favorites\": null,\n \"boomplay_plays\": null,\n \"boomplay_ranking_current\": null,\n \"boomplay_shares\": null,\n \"bs_followers\": 5098140,\n \"deezer_ed_playlist_count\": 410,\n \"deezer_ed_playlist_total_reach\": 16606625,\n \"deezer_fans\": 12799696,\n \"deezer_playlist_count\": 1793,\n \"deezer_playlist_total_reach\": 29093015,\n \"facebook_followers\": 17551796,\n \"fs_likes\": 17551796,\n \"fs_talks\": 164567,\n \"genius_pageviews\": 146897583,\n \"ins_engagement_rate\": 1.8932,\n \"ins_followers\": 75278195,\n \"itunes_ed_playlist_count\": 1479,\n \"itunes_playlist_count\": 4030,\n \"line_music_artist_likes\": 39175,\n \"line_music_likes\": 121504,\n \"line_music_mv_plays\": 7610,\n \"line_music_plays\": 18834566,\n \"melon_artist_fans\": 64626,\n \"melon_likes\": 998788,\n \"melon_video_likes\": 3223,\n \"melon_video_views\": 284132,\n \"pandora_lifetime_stations_added\": 30481713,\n \"pandora_lifetime_streams\": 11089516088,\n \"pandora_listeners_28_day\": 4942003,\n \"shazam_count\": 332903895,\n \"songkick_fans\": 5810426,\n \"soundcloud_followers\": 7022834,\n \"soundcloud_plays\": 811751165,\n \"sp_followers\": 90205700,\n \"sp_followers_to_listeners_ratio\": 0.862086,\n \"sp_listeners_to_followers_ratio\": 1.159978,\n \"sp_monthly_listeners\": 104636584,\n \"sp_popularity\": 94,\n \"spotify_ed_playlist_count\": 1689,\n \"spotify_ed_playlist_total_reach\": 97141939,\n \"spotify_playlist_count\": 2646495,\n \"spotify_playlist_total_reach\": 806946810,\n \"tiktok_followers\": 9600000,\n \"tiktok_likes\": 50200000,\n \"tiktok_top_video_views\": 22291146251,\n \"tiktok_track_posts\": 10215696,\n \"ts_followers\": 17066030,\n \"twitch_followers\": 8882,\n \"twitch_monthly_viewer_hours\": null,\n \"twitch_views\": null,\n \"twitch_weekly_viewer_hours\": null,\n \"ws_views\": 7961,\n \"ycs_subscribers\": 35900000,\n \"ycs_views\": 28277468141,\n \"youtube_daily_video_views\": 16611114,\n \"youtube_ed_playlist_count\": 28,\n \"youtube_ed_playlist_total_reach\": 81551434,\n \"youtube_monthly_video_views\": 440747883,\n \"youtube_playlist_count\": 1190,\n \"youtube_playlist_total_reach\": 8116380417,\n \"de_editorial_playlist_total_reach\": 16606625,\n \"de_playlist_total_reach\": 29093015,\n \"facebook_likes\": 17551796,\n \"facebook_talks\": 164567,\n \"num_am_editorial_playlists\": 1479,\n \"num_am_playlists\": 4030,\n \"num_az_editorial_playlists\": 953,\n \"num_az_playlists\": 1022,\n \"num_de_editorial_playlists\": 410,\n \"num_de_playlists\": 1793,\n \"num_sp_editorial_playlists\": 1689,\n \"num_sp_playlists\": 2646495,\n \"num_yt_editorial_playlists\": 28,\n \"num_yt_playlists\": 1190,\n \"sp_editorial_playlist_total_reach\": 97141939,\n \"sp_playlist_total_reach\": 806946810,\n \"yt_editorial_playlist_total_reach\": 81551434,\n \"yt_playlist_total_reach\": 8116380417,\n \"ts_retweets\": null\n },\n ...\n ],\n \"offset\": 10,\n \"total\": 49\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/list/filter?limit=50&offset=10&sortColumn=cm_artist_rank&sortOrderDesc=false&code2=US&band=false&cpp[]=2&cpp[]=1000000&tt_f[]=11800000&tt_f[]=85000000", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "get", - "url": "/api/artist/:type/list", - "title": "Get Artists", - "version": "1.0.0", - "name": "getArtistListByStats", - "description": "

Get an artists snapshot of sample artists based on a given metric.

Given the metric filter and score range, get a list of artist ids and all relevant metrics for the given range of the respective metric.

This endpoint is useful for artist profiling based on specific filters such as Spotify performance, YouTube performance and social media performance.

", - "group": "Artist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "sp_followers", - "sp_monthly_listeners", - "sp_popularity", - "sp_listeners_to_followers_ratio", - "sp_followers_to_listeners_ratio", - "deezer_fans", - "fs_likes", - "fs_talks", - "ins_followers", - "ts_followers", - "ts_retweets", - "ycs_views", - "ycs_subscribers", - "youtube_daily_video_views", - "youtube_monthly_video_views", - "ws_views", - "soundcloud_followers", - "bs_followers", - "tiktok_followers", - "tiktok_likes", - "cm_artist_rank" - ], - "optional": false, - "field": "type", - "description": "

'cm_artist_rank': Cross Platform Performance (CPP), 'sp_monthly_listeners': Spotify monthly listeners, 'sp_followers': Spotify followers, 'sp_popularity': Spotify popularity, 'sp_listeners_to_followers_ratio': Spotify listeners to followers ratio, 'sp_followers_to_listeners_ratio': Spotify followers to listeners ratio (Conversion ratio), 'deezer_fans': Deezer fans, 'fs_likes': Facebook fan count, 'fs_talks': Facebook people talking about count, 'ts_followers': Twitter followers, 'ts_retweets': Twitter Retweets, 'ins_followers': Instagram Followers, 'ycs_views': YouTube channel views, 'ycs_subscribers': YouTube channel subscribers, 'youtube_daily_video_views': YouTube Daily Video Views, 'youtube_monthly_video_views': YouTube Monthly Video Views 'ws_views': Wikipedia pages views, 'soundcloud_followers': Soundcloud followers, 'bs_followers': BandsInTown followers, 'tiktok_followers': TikTok Followers, 'tiktok_likes': TikTok Likes

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "min", - "description": "

Minimum filter threshold (e.g. min 5,000 for Spotify followers)

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "max", - "description": "

Maximum filter threshold (e.g. max 10,000 for Spotify followers)

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "code2", - "description": "

ISO code for the artist country, such as US, JP, IN, etc

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "genreId", - "description": "

The genre ID to filter the results

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "subGenreId", - "description": "

The sub genre ID to filter the results

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "city", - "description": "

City name to filter the top listeners

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "unsigned", - "defaultValue": "false", - "description": "

Whether to return only unsigned artists

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned. Max value: 200

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns only certain number of entries. Use offset option to get more results. Max value: 100000

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Number of items in the data array.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

List of results

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.chartmetric_artist_id", - "description": "

Chartmetric artist ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

Artist name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.code2", - "description": "

Country codes.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.genres", - "description": "

genres.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.cpp_rank", - "description": "

Cross-Platform Performance rank.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rank_eg", - "description": "

Engagement Rank.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rank_fb", - "description": "

Fan base Rank.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.spotify_popularity", - "description": "

Spotify popularity.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.spotify_followers", - "description": "

Spotify ollowers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.spotify_monthly_listeners", - "description": "

Spotify monthly listeners.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.spotify_listeners_to_followers_ratio", - "description": "

Spotify listeners to followers ratio.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.spotify_followers_to_listeners_ratio", - "description": "

Spotify followers to listeners ratio.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.deezer_fans", - "description": "

Deezer fans.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.facebook_likes", - "description": "

Facebook likes.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.facebook_talks", - "description": "

Facebook talks.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.twitter_followers", - "description": "

Twitter followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.twitter_retweets", - "description": "

Twitter retweets.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.instagram_followers", - "description": "

Instagram followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.youtube_channel_views", - "description": "

YouTube channel views.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.youtube_subscribers", - "description": "

YouTube subscribers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.youtube_daily_video_views", - "description": "

YouTube daily video views.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.youtube_monthly_video_views", - "description": "

YouTube monthly video views.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.wikipedia_views", - "description": "

Wikipedia views.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.soundcloud_followers", - "description": "

Soundcloud followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.bandsintown_followers", - "description": "

Bandsintown followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.tiktok_followers", - "description": "

TikTok followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.tiktok_likes", - "description": "

TikTok likes.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.sp_where_people_listen", - "description": "

Spotify listener count and location.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.sp_where_people_listen.code2", - "description": "

Country code of where people listen.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.sp_where_people_listen.listeners", - "description": "

Number of listeners of the country where people listen.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.sp_where_people_listen.name", - "description": "

Name of the city where people listen.

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "data.signed", - "description": "

Whether the artist has ever been signed to a label.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response (sp_followers)", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"length\": 200,\n \"data\": [\n {\n \"chartmetric_artist_id\": 41457,\n \"name\": \"Neamen Lyles\",\n \"code2\": \"US\",\n \"genres\": \"deep smooth jazz, smooth jazz, jazz\",\n \"cpp_rank\": 375487,\n \"rank_eg\": 451348,\n \"rank_fb\": 282213,\n \"spotify_popularity\": 15,\n \"spotify_followers\": 500,\n \"spotify_monthly_listeners\": 3652,\n \"spotify_listeners_to_followers_ratio\": 7.304,\n \"spotify_followers_to_listeners_ratio\": 0.136911,\n \"deezer_fans\": \"28\",\n \"facebook_likes\": null,\n \"facebook_talks\": null,\n \"twitter_followers\": null,\n \"twitter_retweets\": null,\n \"instagram_followers\": null,\n \"youtube_channel_views\": null,\n \"youtube_subscribers\": null,\n \"youtube_daily_video_views\": null,\n \"youtube_monthly_video_views\": null,\n \"wikipedia_views\": null,\n \"soundcloud_followers\": null,\n \"bandsintown_followers\": null,\n \"tiktok_followers\": null,\n \"tiktok_likes\": null,\n \"sp_where_people_listen\": [\n {\n \"code2\": \"id\",\n \"listeners\": 48,\n \"name\": \"Jakarta\"\n },\n {\n \"code2\": \"us\",\n \"listeners\": 47,\n \"name\": \"Los Angeles\"\n },\n {\n \"code2\": \"us\",\n \"listeners\": 41,\n \"name\": \"Atlanta\"\n },\n {\n \"code2\": \"mx\",\n \"listeners\": 38,\n \"name\": \"Mexico City\"\n },\n {\n \"code2\": \"us\",\n \"listeners\": 32,\n \"name\": \"Chicago\"\n }\n ],\n \"signed\": false\n },\n ...\n ]\n }\n}", - "type": "json" - }, - { - "title": "Success-Response (ycs_subscribers)", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"length\": 5,\n \"data\": [\n {\n \"chartmetric_artist_id\": 131081,\n \"name\": \"OH MY GIRL\",\n \"code2\": \"KR\",\n \"genres\": \"k-pop, k-pop girl group, dance, pop\",\n \"cpp_rank\": 6975,\n \"rank_eg\": 9898,\n \"rank_fb\": 4282,\n \"spotify_popularity\": 56,\n \"spotify_followers\": 1657535,\n \"spotify_monthly_listeners\": 1124085,\n \"spotify_listeners_to_followers_ratio\": 0.678167,\n \"spotify_followers_to_listeners_ratio\": 1.474564,\n \"deezer_fans\": 39240,\n \"facebook_likes\": 663151,\n \"facebook_talks\": 171,\n \"twitter_followers\": 406365,\n \"twitter_retweets\": 0,\n \"instagram_followers\": 1358755,\n \"youtube_channel_views\": 601066964,\n \"youtube_subscribers\": 1020000,\n \"youtube_daily_video_views\": 475666,\n \"youtube_monthly_video_views\": 13694039,\n \"wikipedia_views\": 456,\n \"soundcloud_followers\": null,\n \"bandsintown_followers\": 9225,\n \"tiktok_followers\": 798700,\n \"tiktok_likes\": 8000000,\n \"sp_where_people_listen\": [\n {\n \"code2\": \"id\",\n \"listeners\": 40053,\n \"name\": \"Jakarta\"\n },\n ...\n ],\n \"signed\": true\n },\n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage (sp_followers):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/sp_followers/list?min=500&max=10000&offset=0&code2=US&city=Chicago", - "type": "curl" - }, - { - "title": "Example usage (ycs_subscribers):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/artist/ycs_subscribers/list?min=1000000&max=2000000&code2=KR&city=seoul&limit=5", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/artist.docs.ts", - "groupTitle": "Artist" - }, - { - "type": "post", - "url": "/api/token", - "title": "Get API access token", - "name": "GetAccessToken", - "version": "1.0.0", - "group": "Authorization", - "description": "

Get API access token used to make all other API requests.

\t\t \t\t

If you are experiencing issues with the refresh token, please contact hi@chartmetric.com.

", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "refreshtoken", - "description": "

Chartmetric refresh token*

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "token", - "description": "

Access Token to use in subsequent requests

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "expires_in", - "description": "

Number of seconds after which the token will expire

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "refresh_token", - "description": "

Refresh Token

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "scope", - "description": "

Level of access granted, currently always api

" - } - ] - }, - "examples": [ - { - "title": "HTTP/1.1 200 OK", - "content": "HTTP/1.1 200 OK\n{\n \"token\": \"eyJhbGciOiJIUzI....\"\n \"expires_in\": 3600,\n \"refresh_token\": \"xxxxxx....\",\n \"scope\": \"api\"\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -d '{\"refreshtoken\":\"REFRESH_TOKEN\"}' -H \"Content-Type: application/json\" -X POST https://api.chartmetric.com/api/token", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/root.docs.ts", - "groupTitle": "Authorization" - }, - { - "type": "get", - "url": "/api/brand/:brandId", - "title": "Brand Info", - "version": "1.0.0", - "name": "GetBrandInfo", - "description": "

Returns a list of artists that have followers with interest in a brand, as well as related statistics.

", - "group": "Brand", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "brandId", - "description": "

Chartmetric Brand ID.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "followers", - "likers", - "follower_overlap", - "liker_overlap", - "follower_affinity", - "liker_affinity" - ], - "optional": true, - "field": "sortColumn", - "defaultValue": "followers", - "description": "

Column to sort by.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "sortOrderDesc", - "defaultValue": "true", - "description": "

Whether to sort in descending order.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of artists related to a brand to be returned. Maximum 50.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "brandName", - "description": "

Brand name.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "total", - "description": "

The total number of artists with an audience that likes the specified brand.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artists", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.id", - "description": "

Chartmetric's Artist ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.image_url", - "description": "

Artist image URL.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "artists.instagram_followers", - "description": "" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.instagram_followers.total", - "description": "

Total artist followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.instagram_followers.overlap", - "description": "

Artist followers that are interested in the brand.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "artists.instagram_followers.overlap_percent", - "description": "

Percentage of artist followers that are interested in the brand.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "artists.instagram_followers.affinity", - "description": "

Artist follower's affinity towards the brand. The affinity of a brand is a measure of how much this artist's audience is interested in the brand, as compared to the average artist's audience.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "artists.instagram_likers", - "description": "" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.instagram_likers.average", - "description": "

Average artist likers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.instagram_likers.overlap", - "description": "

Artist likers that are interested in the brand.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "artists.instagram_likers.overlap_percent", - "description": "

Percentage of artist likers that are interested in the brand.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "artists.instagram_likers.affinity", - "description": "

Artist liker's affinity towards the brand. The affinity of a brand is a measure of how much this artist's audience is interested in the brand, as compared to the average artist's audience.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"brandName\": \"Apple\",\n \"total\": 98864,\n \"artists\": [\n {\n \"id\": \"74419\",\n \"name\": \"XXXTENTACION\",\n \"image_url\": \"https://rovimusic.rovicorp.com/image.jpg?c=yuv5C_kbffM_HzyztzV8fAJZhG49AlXxcRVo_RfD3cE=&f=4\",\n \"instagram_followers\": {\n \"total\": 20578742,\n \"overlap\": 1858034,\n \"overlap_percent\": 0.09028899822933782,\n \"affinity\": 1.07031695578559\n },\n \"instagram_likers\": {\n \"average\": 29561689,\n \"overlap\": 1776398,\n \"overlap_percent\": 0.060091221445432295,\n \"affinity\": 0.9514696637904785\n }\n },\n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/brand/138?sortColumn=followers", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/brand.docs.ts", - "groupTitle": "Brand" - }, - { - "type": "get", - "url": "/api/brand/list/by/interest", - "title": "Brand List by Interest", - "version": "1.0.0", - "name": "GetBrandList", - "description": "

Returns a list of brands by interest, sorted by a stat.

", - "group": "Brand", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "likers", - "followers" - ], - "optional": true, - "field": "sortColumn", - "defaultValue": "followers", - "description": "

Column to sort by.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "interest", - "description": "

Interest name. There are up to 20 brands per interest category.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "interest.brand_id", - "description": "

Brand ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "interest.brand_name", - "description": "

Brand name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "interest.interest_id", - "description": "

Interest ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "interest.interest_name", - "description": "

Interest name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "interest.followers", - "description": "

Sum of all artists’ followers who are interested in this brand

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "interest.artist_count", - "description": "

Number of artists who have at least one follower interested in this brand

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"Electronics & Computers\": [\n {\n \"brand_id\": 138,\n \"brand_name\": \"Apple\",\n \"interest_id\": 25,\n \"interest_name\": \"Electronics & Computers\",\n \"followers\": 3305301635,\n \"artist_count\": 141435\n },\n ...\n ],\n \"Music\": [\n ...\n ],\n ...\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/brand/list/by/interest?sortColumn=followers", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/brand.docs.ts", - "groupTitle": "Brand" - }, - { - "type": "get", - "url": "/api/brand/list", - "title": "Brand List", - "version": "1.0.0", - "name": "GetBrandNames", - "description": "

Get a list of all brand names and their respective IDs.

", - "group": "Brand", - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric's Brand ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Brand name

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 2,\n \"name\": \"20th Century Fox\"\n },\n {\n \"id\": 10787,\n \"name\": \"24Classics\"\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/brand/list", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/brand.docs.ts", - "groupTitle": "Brand" - }, - { - "type": "get", - "url": "/api/charts/", - "title": "Introduction", - "name": "ChartIntroduction", - "version": "1.3.0", - "group": "Charts", - "description": "

Chartmetric provides in-depth metrics and historical data for tracks / album on various charts.

Generally, charts data is fetched based on charting country, genre type and charting date. To get the list of countries with available chart data, use the endpoint /api/charts/:platform/countries (Charts - Chart Countries).

The parameter table explains fundamental fields used for Chart routes, as well as show the full option list for country_code.

The example response explains certain important keys found in most Chart routes.

", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "GLOBAL", - "US", - "JP", - "DE", - "FR", - "GB", - "IT", - "CN", - "BR", - "CA", - "ES", - "IN", - "MX", - "NL", - "AU", - "AR", - "KR", - "RU", - "CH", - "TW", - "BE", - "SE", - "AT", - "TR", - "DK", - "HK", - "PL", - "NO", - "SA", - "FI", - "GR", - "ZA", - "TH", - "PT", - "CO", - "IL", - "VE", - "SG", - "ID", - "EG", - "IE", - "CL", - "MY", - "NG", - "PH", - "PE", - "CZ", - "NZ", - "HU", - "UA", - "RO", - "AE", - "KZ", - "VN", - "EC", - "SI", - "GT", - "LB", - "LU", - "DO", - "LK", - "BG", - "SV", - "LT", - "CR", - "CY", - "KE", - "PA", - "BO", - "PY", - "JO", - "LV", - "ZW", - "HN", - "EE", - "KH", - "PG", - "NI", - "MD", - "LA", - "SZ", - "MN" - ], - "optional": false, - "field": "country_code", - "description": "

(Not all charts contain 'GLOBAL')

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "All Genres", - "Rock", - "Country", - "..." - ], - "optional": false, - "field": "genre", - "description": "" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

All charts use the date field to determine the charting day in the format YYYY-MM-DD, e.g. 2018-12-31.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "" - }, - { - "group": "Parameter", - "type": "Number", - "optional": false, - "field": "limit", - "description": "

Maximum 200.

" - } - ] - } - }, - "success": { - "examples": [ - { - "title": "Example Response (Result Explanation):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"length\": 50, => Number of data entries\n \"data\": [ => List of results\n {\n \"id\": 9918647, => Specific Chart ID (Spotify, iTunes...)\n \"cm_track\": .., => Chartmetric Track ID\n \"cm_album\": .., => Chartmetric Album ID,\n \"cm_artist\": .., => Chartmetric Artist ID,\n \"added_at\": .., => Date Of Data Recorded\n \"rank\": .., => Current Rank\n \"pre_rank\": .., => Previous Rank (To compute rank change)\n \"peak_rank\": .., => Top Rank\n \"peak_date\": .., => Date of Top Rank\n \"time_on_chart\": .., => Number of days on Chart\n \"rankStats\": [ => Historical rank data over time\n {\n \"plays\": .., => Number of streams (Spotify only)\n \"rank\": .., => Rank on Given Date\n \"timestp\": .., => Date of Rank Recorded\n },\n {...},\n ],\n },\n {..},\n {..},\n ...\n ],\n }\n}", - "type": "json" - } - ] - }, - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/airplay/:chart_type", - "title": "Airplay", - "name": "GetAirplayChart", - "version": "1.0.0", - "description": "

Get data insights for tracks or artists on Airplay Charts, including latest position, time on chart and position changes over time.

Given the appropriate filters, get data specific to country and type depending on the chart type.

This endpoint is useful for identifying the music trends on Airplay Charts in different countries.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "tracks", - "artists" - ], - "optional": false, - "field": "chart_type", - "description": "" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Charting date

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "since", - "description": "

Start of date range, in ISO date format (e.g. "2017-03-25").

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "limit", - "description": "

The limit of entries to be returned.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "daily", - "weekly" - ], - "optional": false, - "field": "duration", - "description": "" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "US", - "JP", - "..." - ], - "optional": true, - "field": "country_code", - "defaultValue": "GLOBAL", - "description": "

country_code Country Code (Full list in Introduction)

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Number of data entries.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

List of results.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist id.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Artist name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Artist image url.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isni", - "description": "

Artist isni.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "code2", - "description": "

Country code.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "hometown_city", - "description": "

Hometown city.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "current_city", - "description": "

Current city.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_followers", - "description": "

Spotify followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_popularity", - "description": "

Spotify popularity.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "deezer_fans", - "description": "

Deezer fans.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "tags", - "description": "

List of genre tags.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_artist_ids", - "description": "

Spotify artist ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "itunes_artist_ids", - "description": "

iTunes artist ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "deezer_artist_ids", - "description": "

Deezer artist ids.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_artist_rank", - "description": "

Chartmetric artist rank.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "amazon_artist_ids", - "description": "

Amazon artist ids.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

Current rank.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "plays", - "description": "

Number of streams.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_artist", - "description": "

Chartmetric artist id.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "added_at", - "description": "

Added date.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "velocity", - "description": "

Velocity.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "pre_rank", - "description": "

Previous rank.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "peak_rank", - "description": "

Top Rank.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "peak_date", - "description": "

Date of Top Rank.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "time_on_chart", - "description": "

Time on the chart.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "rankStats", - "description": "

Rank object containing rank statistics.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rankStats.rank", - "description": "

Rank value

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rankStats.plays", - "description": "

Number of plays

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "rankStats.timestp", - "description": "

time stamp.

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"length\": 500,\n \"data\": [\n {\n \"id\": 5381,\n \"name\": \"Dua Lipa\",\n \"image_url\": \"https://i.scdn.co/image/552c454001ba87ea580c1f084482991502562996\",\n \"isni\": \"0000000464135414\",\n \"code2\": \"gb\",\n \"hometown_city\": \"London\",\n \"current_city\": \"London\",\n \"sp_followers\": 24541621,\n \"sp_popularity\": 95,\n \"sp_monthly_listeners\": 64603939,\n \"deezer_fans\": 5777155,\n \"tags\": [\n \"pop\",\n \"dance pop\",\n \"post-teen pop\",\n \"uk pop\"\n ],\n \"spotify_artist_ids\": [\n \"6M2wZ9GZgrQXHCFfjv46we\"\n ],\n \"itunes_artist_ids\": [\n 1031397873,\n 399837637\n ],\n \"deezer_artist_ids\": [\n \"8706544\"\n ],\n \"cm_artist_rank\": 8,\n \"amazon_artist_ids\": [\n \"B0143OIF9G\"\n ],\n \"rank\": 1,\n \"plays\": 9510,\n \"cm_artist\": 5381,\n \"added_at\": \"2021-05-05T07:00:00.000Z\",\n \"velocity\": 0.142857142857143,\n \"pre_rank\": 2,\n \"peak_rank\": 1,\n \"peak_date\": \"2020-03-08T08:00:00.000Z\",\n \"time_on_chart\": \"491\",\n \"rankStats\": [\n {\n \"rank\": 2,\n \"plays\": 5158,\n \"timestp\": \"2021-04-08T07:00:00.000Z\"\n },\n {\n \"rank\": 2,\n \"plays\": 5051,\n \"timestp\": \"2021-04-09T07:00:00.000Z\"\n },\n {\n \"rank\": 2,\n \"plays\": 5408,\n \"timestp\": \"2021-04-10T07:00:00.000Z\"\n },\n ...\n ]\n },\n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Usage (artists)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/airplay/artists?since=2020-09-09&duration=daily", - "type": "curl" - }, - { - "title": "Usage (tracks)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/airplay/tracks?since=2020-09-09&duration=daily", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/amazon/:chart-type", - "title": "Amazon", - "name": "GetAmazonMusicChart", - "version": "1.0.0", - "description": "

Get chart insights for Top albums or Top 500 Tracks on Amazon Charts, including information such as peak rank and time on chart.

The insights can be filtered by country and chart type using query parameters.

These insights can help identify the latest trends on Prime Music across the world.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "tracks", - "albums" - ], - "optional": false, - "field": "chart-type", - "description": "" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "popular_track", - "new_track", - "popular_album", - "new_album" - ], - "optional": false, - "field": "type", - "description": "

['popular_track', 'new_track'] (tracks)
['popular_album', 'new_album'] (albums)

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Charting Date. This field is required unless the latest parameter is set

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "code2", - "description": "

The ISO Alpha-2 Country Code of the Amazon Chart

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "All Genres", - "Alternative Rock", - "Blues", - "Broadway & Vocalists", - "Children's Music", - "Christian", - "Classic Rock", - "Classical", - "Country", - "Dance & Electronic", - "Folk", - "Hard Rock & Metal", - "Holiday", - "International", - "Jazz", - "Latin Music", - "Miscellaneous", - "New Age", - "Opera & Vocal", - "Pop", - "R&B", - "Rap & Hip-Hop", - "Rock", - "Soundtracks" - ], - "optional": false, - "field": "genre", - "description": "" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Number of data entries.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

List of results.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.id", - "description": "

Amazon id.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

Track name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.isrc", - "description": "

Track isrc.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.image_url", - "description": "

image url.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.amazon_artist_id", - "description": "

Amazon artist id.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.amazon_album_id", - "description": "

Amazon album id.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.cm_track", - "description": "

chartmetric track id.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.amazon_artist_names", - "description": "

Amazon artist names.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.cm_artist", - "description": "

Chartmetric artist id.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_names", - "description": "

Artist names.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.code2s", - "description": "

Country codes.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_images", - "description": "

Artist images.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_covers", - "description": "

Artist covers.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.amazon_artist_ids", - "description": "

Amazon artist ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.amazon_track_ids", - "description": "

Amazon track ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.amazon_album_ids", - "description": "

Amazon album ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.amazon_genres", - "description": "

Amazon genres.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_ids", - "description": "

Album ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_names", - "description": "

Album names.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_upc", - "description": "

Album upc.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_label", - "description": "

Album label.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.added_at", - "description": "

Added date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.amazon_browse", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.amazon", - "description": "

Amazon id

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.velocity", - "description": "

Velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.pre_rank", - "description": "

Previous rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.peak_rank", - "description": "

Top rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.peak_date", - "description": "

Date of top rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.time_on_chart", - "description": "

Track time on chart

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.rankStats", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rankStats.rank", - "description": "

Rank of the track

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.rankStats.timestp", - "description": "

Timestamp of the rank

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": {\n \"length\": 100,\n \"data\": [\n {\n \"id\": \"B07KZ6D8VM\",\n \"name\": \"Eastside\",\n \"isrc\": \"USUM71809132\",\n \"image_url\": \"https://m.media-amazon.com/images/I/511Dta5TMmL.jpg\",\n \"amazon_artist_id\": \"B07FK4HZWB\",\n \"amazon_album_id\": \"B07KZB96J9\",\n \"cm_track\": 20184254,\n \"amazon_artist_names\": [\n \"benny blanco\"\n ],\n \"cm_artist\": [\n 1118101\n ],\n \"artist_names\": [\n \"benny blanco\"\n ],\n \"code2s\": [\n \"US\"\n ],\n \"artist_images\": [\n \"https://images-na.ssl-images-amazon.com/images/I/81Nn1XQ7FsL.jpg\"\n ],\n \"artist_covers\": [\n null\n ],\n \"amazon_artist_ids\": [\n \"B07FK4HZWB\"\n ],\n \"amazon_track_ids\": [\n \"B07KZ6D8VM\"\n ],\n \"amazon_album_ids\": [\n \"B07KZB96J9\"\n ],\n \"amazon_genres\": [\n \"Pop/General\"\n ],\n \"album_ids\": [\n null\n ],\n \"album_names\": [\n \"FRIENDS KEEP SECRETS [Explicit]\"\n ],\n \"album_upc\": [\n null\n ],\n \"album_label\": [\n \"Friends Keep Secrets/Interscope Records\"\n ],\n \"rank\": 100,\n \"added_at\": \"2019-05-26T00:00:00.000Z\",\n \"amazon_browse\": \"\",\n \"amazon\": \"B07KZ6D8VM\",\n \"velocity\": -0.142857142857143,\n \"pre_rank\": 100,\n \"peak_rank\": 83,\n \"peak_date\": \"2019-05-06T00:00:00.000Z\",\n \"time_on_chart\": \"18\",\n \"rankStats\": [\n {\n \"rank\": 99,\n \"timestp\": \"2019-05-19T00:00:00.000Z\"\n },\n {\n \"rank\": 100,\n \"timestp\": \"2019-05-22T00:00:00.000Z\"\n }, ...\n ]\n }, ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Usage (tracks)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/amazon/tracks?date=2019-5-26&type=popular_track&genre=All+Genres", - "type": "curl" - }, - { - "title": "Usage (albums)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/amazon/albums?date=2019-5-26&type=popular_album&genre=All+Genres", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/anghami/track/:chartType", - "title": "Anghami (Tracks)", - "name": "GetAnghamiTracksChart", - "version": "1.0.0", - "description": "

Get data insights for tracks on Anghami Tracks Charts, including historical chart positions trend and streaming counts over time.
This endpoint is useful for identifying latest music trends in Anghami Tracks Charts.

chartType genre code2
play All Genres, African, Alternative, Arabic, Arabic Hip Hop, Arabic Indie, Arabic Oldies, Bollywood, Country, Dabkeh, Dance, Dance Arabia, Egyptian, Electronica, Folk, French, Hiphop, Indie, International, International Oldies, Iraqi, Italian, Khaleeji, Kids, Kpop, Latino, Lebanese, Levant, Mahraganat, Metal, Music Videos, Pinoy, Pop, Rai, R B, Rock, Shelat, Tarab, Titrat Ramadan, Turkish 00(GLOBAL), AE, BH, DZ, EG, IQ, JO, KW, LB, LY, MA, OM, PS, QA, SA, SD, SY, TN, YE
like All Genres, Arabic, International 00(GLOBAL), AE, EG, SA

Note: For Anghami charts, either genre or code2 can be used at a single request.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "play", - "like" - ], - "optional": false, - "field": "chartType", - "description": "

Chart type

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "description": "

Charting Date. Use /chart/dates endpoint to get available dates. Available dates are supported by anghami_plays and anghami_likes streamingType.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Genre filter. See the above table for available values.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "code2", - "description": "

Country code filter. See the above table for available values.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

The limit of entries to be returned.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entityId", - "description": "

Internal system ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "targetId", - "description": "

Anghami track ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "duration", - "description": "

Chart duration

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "country", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cityId", - "description": "

City ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Chart genre

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rankDiff", - "description": "

Rank difference from previous period

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "rankTrend", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.timestp", - "description": "

Time stamp

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.rank", - "description": "

Rank at given date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "peakDate", - "description": "

Date of peak rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "peakRank", - "description": "

Top rank achieved

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "velocity7Day", - "description": "

7-day velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "periodsOnChart", - "description": "

Total periods on chart

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "consecutivePeriods", - "description": "

Consecutive periods on chart

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "chartedDate", - "description": "

Date when track first charted

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "imageUrl", - "description": "

Track image URL

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "createdAt", - "description": "

Creation timestamp

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artists", - "description": "

Artist information

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.careerStage", - "description": "

Artist career stage

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.code2", - "description": "

Artist country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.imageUrl", - "description": "

Artist image URL

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "album", - "description": "

Album information

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "album.id", - "description": "

Chartmetric album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.name", - "description": "

Album name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "album.releaseDate", - "description": "

Album release date

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "optionalRows", - "description": "

Optional additional data

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "targetExtras", - "description": "

Platform-specific metrics

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "isChartGenre", - "description": "

Whether the genre field is indicating the chart genre as opposed to the entity (track/album) genre

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": [\n {\n \"entityId\": 15191225,\n \"targetId\": \"44638780\",\n \"country\": \"00\",\n \"cityId\": 0,\n \"genre\": \"Country\",\n \"rank\": 1,\n \"rankDiff\": 0,\n \"rankTrend\": [\n {\n \"timestp\": \"2025-06-29\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-06-30\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-07-01\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-07-02\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-07-03\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-07-04\",\n \"rank\": \"1\"\n }\n ],\n \"peakDate\": \"2023-09-14\",\n \"peakRank\": 1,\n \"velocity7Day\": 0,\n \"periodsOnChart\": 622,\n \"consecutivePeriods\": 622,\n \"chartedDate\": \"2025-07-04\",\n \"name\": \"Love Story\",\n \"imageUrl\": \"https://i.scdn.co/image/1558776870134bc39c55fd278f2748d338eb7887\",\n \"createdAt\": \"2025-07-07 10:44:56\",\n \"artists\": [\n {\n \"careerStage\": \"superstar\",\n \"code2\": \"US\",\n \"id\": 2762,\n \"name\": \"Taylor Swift\",\n \"imageUrl\": \"https://share.chartmetric.com/artists/299/172/11172299/11172299-profile.webp\"\n }\n ],\n \"album\": {\n \"id\": 10998,\n \"name\": \"Fearless\",\n \"label\": \"Big Machine Records\",\n \"releaseDate\": \"2008-01-01T00:00:00.000Z\"\n },\n \"optionalRows\": {\n \"overallRank\": 0,\n \"videos\": 0,\n \"totalVideos\": 0,\n \"metricValue\": 0,\n \"metricDiff\": null,\n \"avgPosition\": 0,\n \"score\": 0,\n \"totalFollowers\": 0,\n \"velocity\": null,\n \"isComeback\": false,\n \"plays\": 0,\n \"playsWeekly\": 0,\n \"counts\": 0,\n \"index\": 0,\n \"comments\": 0\n },\n \"targetExtras\": {\n \"airplayStreams\": 1656,\n \"amazonEdPlaylistCount\": 47,\n \"amazonPlaylistCount\": 47,\n \"anghamiLikes\": 64284,\n \"anghamiPlays\": 1788070,\n \"deezerEdPlaylistCount\": 1,\n \"deezerEdPlaylistTotalReach\": 22362,\n \"deezerPlaylistCount\": 12,\n \"deezerPlaylistTotalReach\": 35358,\n \"itunesEdPlaylistCount\": 49,\n \"itunesPlaylistCount\": 101,\n \"melonComments\": 103,\n \"melonLikes\": 57386,\n \"pandoraLifetimeStationsAdded\": 225626,\n \"pandoraLifetimeStreams\": 167946031,\n \"siriusxmStreams\": 152,\n \"soundcloudPlays\": 1125308,\n \"spotifyEdPlaylistCount\": 25,\n \"spotifyEdPlaylistTotalReach\": 1944454,\n \"spotifyPlaylistCount\": 125693,\n \"spotifyPlaylistTotalReach\": 34599366,\n \"spotifyPlays\": 1013401688,\n \"spotifyPopularity\": 78,\n \"tiktokTopVideosComments\": 392893,\n \"tiktokTopVideosLikes\": 43053248,\n \"tiktokTopVideosViews\": 407550009,\n \"youtubeComments\": 250924,\n \"youtubeEdPlaylistCount\": 1,\n \"youtubeEdPlaylistTotalReach\": 116576,\n \"youtubeLikes\": 6453798,\n \"youtubePlaylistCount\": 34,\n \"youtubePlaylistTotalReach\": 194303850,\n \"youtubeViews\": 884447923\n },\n \"isChartGenre\": true\n },\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/anghami/track/play?genre=country&date=2025-07-06", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/applemusic/:chart-type", - "title": "Apple Music", - "name": "GetAppleMusicChart", - "version": "1.0.0", - "description": "

Get data insights for tracks, albums or videos on Apple Music Charts, including infomation such as peak rank and historical charting rank changes over time.

Given the appropriate filters, get data specific to country, genre and type depending on the chart type.

This endpoint is useful for identifying the latest trends on Apple Music Charts across genres in different countries.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "tracks", - "albums", - "videos" - ], - "optional": false, - "field": "chart-type", - "description": "" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "top", - "daily" - ], - "optional": false, - "field": "type", - "description": "

Chart type. Allowed only for :chart-type=tracks.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "US", - "JP", - "..." - ], - "optional": false, - "field": "country_code", - "description": "

Full list in Introduction

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "city_id", - "description": "

Chartmetric City ID. Required for Apple Music City Chart

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Charting Date

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "All Genres", - "Pop", - "Rock", - "Dance", - "Latino", - "K-Pop", - "Singer/Songwriter", - "Hip-Hop/Rap", - "Jazz", - "Electronic", - "R&B/Soul", - "Blues", - "Country", - "Reggae", - "Classical", - "Alternative", - "World", - "Disney", - "J-Pop", - "Christian & Gospel", - "Easy Listening", - "Children's Music", - "Fitness & Workout", - "Soundtrack" - ], - "optional": true, - "field": "genre", - "description": "

If chart-type=tracks, may only be defined when type is top.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "defaultValue": "false", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Number of entries

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

List of results

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.id", - "description": "

Apple Music Chart id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.isrc", - "description": "

Track isrc

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_album_id", - "description": "

iTunes album id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.composer_name", - "description": "

Composer names

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.cm_track", - "description": "

Chartmetric track id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.track_genre", - "description": "

Track genre

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.cm_artist", - "description": "

Chartmetric artist id

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_names", - "description": "

Artist names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.code2s", - "description": "

Country codes

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_images", - "description": "

Artist images

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_covers", - "description": "

Artist covers

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_artist_ids", - "description": "

iTunes artist ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_artist_names", - "description": "

iTunes artist names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_track_ids", - "description": "

iTunes track ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_album_ids", - "description": "

iTunes album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.storefronts", - "description": "

Storefronts

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_ids", - "description": "

Chartmetric album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_names", - "description": "

Album names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_upc", - "description": "

Album upc

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_label", - "description": "

Album labels

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.release_dates", - "description": "

Release dates

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.added_at", - "description": "

Added date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.code2", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.country", - "description": "

Country

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.itunes", - "description": "

iTunes

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.velocity", - "description": "

Velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.pre_rank", - "description": "

Previous rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.peak_rank", - "description": "

Top rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.peak_date", - "description": "

Date of top rank

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.time_on_chart", - "description": "

Track time on chart

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.rankStats", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rankStats.rank", - "description": "

Rank of the track

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.rankStats.timestp", - "description": "

Time stamp of the rank

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": {\n \"length\": 200,\n \"data\": [\n {\n \"id\": 1019178,\n \"name\": \"White Ferrari\",\n \"isrc\": \"QZ5C81600014\",\n \"itunes_album_id\": [\n \"1146195596\"\n ],\n \"composer_name\": null,\n \"image_url\": \"http://is3.mzstatic.com/image/thumb/Music22/v4/08/e1/da/08e1dab2-375d-5491-83c1-77754dc2eb37/BlondCover-Final.jpg/170x170bb-85.jpg\",\n \"cm_track\": 13976781,\n \"track_genre\": \"Pop\",\n \"cm_artist\": [\n 3849\n ],\n \"artist_names\": [\n \"Frank Ocean\"\n ],\n \"code2s\": [\n \"US\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/7db34c8aace6feb91f38601bb75e6b3301b4657a\"\n ],\n \"artist_covers\": [\n null\n ],\n \"itunes_artist_ids\": [\n 442122051\n ],\n \"itunes_artist_names\": [\n \"Frank Ocean\"\n ],\n \"itunes_track_ids\": [\n \"1146195725\"\n ],\n \"itunes_album_ids\": [\n \"1146195596\"\n ],\n \"storefronts\": [\n \"US\"\n ],\n \"album_ids\": [\n 315788\n ],\n \"album_names\": [\n \"Blonde\"\n ],\n \"album_upc\": [\n \"862160000302\"\n ],\n \"album_label\": [\n \"℗ Boys Don't Cry\"\n ],\n \"release_dates\": [\n \"2016-08-20T00:00:00.000Z\"\n ],\n \"rank\": 199,\n \"added_at\": \"2020-11-01T00:00:00.000Z\",\n \"code2\": \"US\",\n \"genre\": \"Pop\",\n \"country\": \"US\",\n \"itunes\": 1019178,\n \"velocity\": -7.42857142857143,\n \"pre_rank\": 175,\n \"peak_rank\": 74,\n \"peak_date\": \"2020-12-09T00:00:00.000Z\",\n \"time_on_chart\": \"245\",\n \"rankStats\": [\n {\n \"rank\": 147,\n \"timestp\": \"2020-10-25T00:00:00.000Z\"\n },\n {\n \"rank\": 147,\n \"timestp\": \"2020-10-26T00:00:00.000Z\"\n }, ...\n ]\n }, ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Usage (tracks)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/applemusic/tracks?date=2020-11-01&country_code=US&type=top&genre=Pop&offset=0", - "type": "curl" - }, - { - "title": "Usage (albums)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/applemusic/albums?date=2018-11-01&country_code=US&genre=Pop&offset=0", - "type": "curl" - }, - { - "title": "Usage (videos)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/applemusic/videos?date=2018-11-01&country_code=US&genre=Pop&offset=0", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/beatport", - "title": "Beatport", - "name": "GetBeatportChart", - "version": "1.0.0", - "description": "

Gather data insights on tracks on Beatport Charts, including latest position, time on chart and position changes over time.

Given the appropriate filters, get data specific to chart genre.

This endpoint is extremely useful for identifying upcoming and indie music that is usually not charting on other streaming services.

This data is updated weekly only (on Fridays).

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Charting Date

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "top-100", - "house", - "tech-house", - "future-house", - "electronica-downtempo", - "progressive-house", - "electro-house", - "techno", - "glitch-hop", - "drum-and-bass", - "trance", - "reggae-dancehall-dub", - "hip-hop-r-and-b", - "indie-dance-nu-disco", - "hard-dance", - "dubstep", - "deep-house", - "psy-trance", - "big-room", - "minimal-deep-tech", - "funky-groove-jackin-house", - "funk-soul-disco", - "dance", - "breaks", - "dj-tools", - "hardcore-hard-techno", - "leftfield-house-and-techno" - ], - "optional": false, - "field": "genre", - "description": "" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Number of entries

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

List of results

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.id", - "description": "

Beatport Chart id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.isrc", - "description": "

Track isrc

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.description", - "description": "

Description

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.created_at", - "description": "

Date created

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.modified_at", - "description": "

Date modified

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.tags", - "description": "

genres

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.cm_track", - "description": "

Chartmetric track id

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.cm_artist", - "description": "

Chartmetric artist id

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_names", - "description": "

Artist names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.code2s", - "description": "

Country codes

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_images", - "description": "

Artist images

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_covers", - "description": "

Artist covers

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.spotify_track_ids", - "description": "

Spotify track ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.spotify_album_ids", - "description": "

Spotify album ids

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.spotify_duration_ms", - "description": "

Spotify duration

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_track_ids", - "description": "

iTunes track ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_album_ids", - "description": "

iTunes album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.storefronts", - "description": "

Storefronts

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_ids", - "description": "

Chartmetric album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_names", - "description": "

Album names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_upc", - "description": "

Album upc

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_label", - "description": "

Album labels

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.release_dates", - "description": "

Release dates

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.added_at", - "description": "

Added date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.genre", - "description": "

Genre

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.beatport", - "description": "

Beatport

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.remixers", - "description": "

Remixers

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.bc_label", - "description": "

label

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.track_name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.artist_name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.velocity", - "description": "

Velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.pre_rank", - "description": "

Previous rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.peak_rank", - "description": "

Top rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.peak_date", - "description": "

Peak date of top rank

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.time_on_chart", - "description": "

Track time on chart

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.rankStats", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rankStats.rank", - "description": "

Rank of the track

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.rankStats.timestp", - "description": "

Track time stamp

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": {\n \"length\": 90,\n \"data\": [\n {\n \"id\": 22223567,\n \"image_url\": \"https://i.scdn.co/image/a2838f16eee45f75767e79a7e02f4fbd9b2bc018\",\n \"isrc\": \"DEDH71800045\",\n \"name\": \"Upperground\",\n \"description\": null,\n \"created_at\": null,\n \"modified_at\": \"2020-07-02T22:46:13.804Z\",\n \"tags\": \"Techno,Music,Dance\",\n \"cm_track\": 22223567,\n \"cm_artist\": [\n 725162,\n 214563\n ],\n \"artist_names\": [\n \"ARTBAT\",\n \"Dino Lenny\"\n ],\n \"code2s\": [\n null,\n \"GB\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/002b83b3f9575d2efa5f9951c3f1020b2fdcb7b1\",\n \"https://i.scdn.co/image/08e2a878127ec93bf3a625c419c3bf498e13b50d\"\n ],\n \"artist_covers\": [\n null,\n null\n ],\n \"spotify_track_ids\": [\n \"657KVeLewbh5Z0p2qEt0EJ\"\n ],\n \"spotify_album_ids\": [\n \"4NiPtdL8Q50ReNepHceCpy\"\n ],\n \"spotify_duration_ms\": 405737,\n \"itunes_track_ids\": [\n \"1446018501\"\n ],\n \"itunes_album_ids\": [\n \"1446018494\"\n ],\n \"storefronts\": [\n \"LA\"\n ],\n \"deezer_track_ids\": [\n \"601810442\"\n ],\n \"deezer_album_ids\": [\n 81332322\n ],\n \"deezer_duration\": 405,\n \"amazon_track_ids\": null,\n \"amazon_album_ids\": null,\n \"album_ids\": [\n 3479875\n ],\n \"album_names\": [\n \"Upperground - EP\"\n ],\n \"album_upc\": [\n \"4018939360603\"\n ],\n \"album_label\": [\n \"Diynamic Music\"\n ],\n \"release_dates\": [\n \"2019-01-04T00:00:00.000Z\"\n ],\n \"rank\": 1,\n \"added_at\": \"2019-02-01T00:00:00.000Z\",\n \"genre\": \"top-100\",\n \"beatport\": 89598,\n \"remixers\": \"\",\n \"bc_label\": \"Diynamic\",\n \"track_name\": \"Upperground\",\n \"artist_name\": \"ARTBAT\",\n \"velocity\": 0,\n \"pre_rank\": 1,\n \"peak_rank\": 1,\n \"peak_date\": \"2019-01-25T00:00:00.000Z\",\n \"time_on_chart\": \"29\",\n \"rankStats\": [\n {\n \"rank\": 1,\n \"timestp\": \"2019-01-25T00:00:00.000Z\"\n },\n {\n \"rank\": 1,\n \"timestp\": \"2019-02-01T00:00:00.000Z\"\n }\n ]\n }, ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/beatport?date=2019-02-01&genre=top-100&offset=0", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/:platform/countries", - "title": "Chart Countries", - "name": "GetChartCountriesForPlatform", - "version": "1.0.0", - "description": "

Returns the countries with available chart data for different chart types. The chart type is selected with the parameters chart_type,type, and duration, depending on the platform.

Note: The chart_type query parameter in this route corresponds to the /:chart-type route parameter of the related chart endpoints.

Airplay

Chart countries for the endpoint: /api/charts/airplay/:chart-type. Accepted parameters:

  • chart_type: artists and tracks.
  • duration: daily and weekly.

Amazon

Chart countries for the endpoint: /api/charts/amazon/:chart-type. Accepted parameters:

  • chart_type: albums and tracks.
  • type: new_album, new_track, popular_album, and popular_track.

Applemusic

Chart countries for the endpoint: /api/charts/applemusic/:chart-type. Accepted parameters:

  • chart_type: albums, tracks, and videos.
  • type (allowed only for chart_type=tracks):top and daily.

Deezer

Chart countries for the endpoint: /api/charts/deezer.
No parameters are required, there is a single chart type for this platform.

iTunes

Chart countries for the endpoint: /api/charts/itunes/:chart-type. Accepted parameters:

  • chart_type: albums, tracks, and videos.

Shazam

Chart countries for the endpoint: /api/charts/shazam.
No parameters are required, there is a single chart type for this platform.

Soundlcoud

Chart countries for the endpoint: /api/charts/soundlcloud.
No parameters are required, there is a single chart type for this platform.

Spotify

Chart countries for the endpoint Charts - Spotify (Tracks): /api/charts/spotify. Accepted parameters:

  • chart_type: tracks.

TikTok

Chart countries for the endpoint: /api/charts/tiktok/:chart-type. Accepted parameters:

  • chart_type: tracks.

YouTube

Chart countries for the endpoint: /api/charts/youtube/:chart-type. Accepted parameters:

  • chart_type: artists, tracks, trends, and videos.

See that not all the possible chart_type and type variations for each chart endpoint are accepted. Only those that accept country codes.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "airplay", - "amazon", - "applemusic", - "deezer", - "itunes", - "shazam", - "soundcloud", - "spotify", - "tiktok", - "youtube" - ], - "optional": false, - "field": "platform", - "description": "

Chart platform.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "albums", - "artists", - "tracks", - "trends", - "videos" - ], - "optional": false, - "field": "chart_type", - "description": "

Desired chart type.
Note: This parameter is required for some platforms, while it is not allowed for others. The accepted values depend on the platform. See description above for a detailed explanation of the accepted values for each platform.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "daily", - "new_album", - "new_track", - "popular_album", - "popular_track", - "top" - ], - "optional": false, - "field": "type", - "description": "

Chart sub-type. Only the following platforms accept this parameter:

  • Amazon: new_album, new_track, popular_album, popular_track.
  • Applemusic: top and daily (allowed only when chart_type=tracks).

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "daily", - "weekly" - ], - "optional": false, - "field": "duration", - "description": "

Chart type duration. Required only for platform=airplay.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "countries", - "description": "

Array of country code2 with available data for the requested chart type.

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": {\n \"countries\": [\n \"PA\",\n \"KN\",\n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Airplay", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/airplay/countries?chart_type=tracks&duration=weekly", - "type": "curl" - }, - { - "title": "Deezer", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/deezer/countries", - "type": "curl" - }, - { - "title": "Spotify", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/spotify/countries?chart_type=tracks&type=viral", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/:streamingType/dates", - "title": "Chart Dates", - "name": "GetChartDatesForstreamingType", - "version": "1.0.0", - "description": "

Returns the dates with available chart data for different streaming types.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "acr_daily_artist", - "acr_weekly_artist", - "acr_daily_track", - "acr_weekly_track", - "amazon", - "amazon_albums", - "amazon_new_tracks", - "apple_music", - "apple_music_daily", - "apple_music_newcomers", - "apple_music_albums", - "apple_music_videos", - "beatport", - "deezer", - "qq", - "shazam", - "soundcloud", - "soundcloud_trending", - "spotify_artist", - "fresh_finds", - "spotify_tracks", - "tiktok", - "tiktok_daily", - "tiktok_weekly", - "tiktok_video", - "tiktok_user_likes", - "tiktok_user_followers", - "tiktok_weekly_user_likes", - "tiktok_weekly_user_followers", - "tiktok_daily_user_likes", - "tiktok_daily_user_followers", - "tiktok_country_weekly", - "twitch_user_followers", - "twitch_monthly_viewer_hours", - "twitch_weekly_user_followers", - "twitch_weekly_viewer_hours", - "youtube_tracks", - "youtube_videos", - "youtube_videos_daily", - "youtube_trends", - "youtube_artists", - "itunes", - "itunes_videos", - "itunes_albums", - "melon_daily", - "melon_weekly", - "melon_monthly", - "melon_yearly", - "melon", - "hanteo", - "line_daily", - "line_weekly", - "line_daily_albums", - "line_weekly_albums", - "pandora_top_spins", - "pandora_top_thumbs", - "anghami_plays", - "anghami_likes", - "circle" - ], - "optional": false, - "field": "streamingType", - "description": "

Streaming type.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": false, - "field": "fromDaysAgo", - "description": "

Number of days to look back for chart data. (e.g. when fromDaysAgo is 9999, returns all available chart dates from that many days ago up to today.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "track", - "album" - ], - "optional": false, - "field": "chartEntity", - "description": "

Chart entity. This parameter is only required for some charts that are documented in the corresponding chart endpoint.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "chartType", - "description": "

Chart type. See each chart endpoint for available values. This parameter is only required for some charts that are documented in the corresponding chart endpoint.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "duration", - "description": "

Chart duration. See each chart endpoint for available values. This parameter is only required for some charts that are documented in the corresponding chart endpoint.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "country", - "description": "

Chart country code2. See each chart endpoint for available values. This parameter is only required for some charts that are documented in the corresponding chart endpoint.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Chart genre. See each chart endpoint for available values. This parameter is only required for some charts that are documented in the corresponding chart endpoint.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "obj", - "description": "

Array of objects with available chart dates for the requested streaming type.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.timestp", - "description": "

Timestamp of the chart date.

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": [\n {\n \"timestp\": \"2025-04-23T07:00:00.000Z\"\n },\n {\n \"timestp\": \"2025-04-13T07:00:00.000Z\"\n },\n {\n \"timestp\": \"2025-04-06T07:00:00.000Z\"\n },\n {\n \"timestp\": \"2025-03-30T07:00:00.000Z\"\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Dates of Spotify Weekly Chart", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/spotify_weekly/dates?fromDaysAgo=9999", - "type": "curl" - }, - { - "title": "Dates of TikTok Weekly Chart by Country", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com//api/charts/tiktok_country_weekly/dates?fromDaysAgo=30", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/genres/:platform", - "title": "Chart Genres", - "name": "GetChartGenresForPlatform", - "version": "1.0.0", - "group": "Charts", - "description": "

Returns available genres for a given platform and country.

", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "amazon", - "amazon_albums", - "apple_music", - "apple_music_albums", - "apple_music_videos", - "beatport", - "itunes", - "itunes_albums", - "itunes_videos", - "line_daily", - "melon_daily", - "melon_monthly", - "melon_weekly", - "melon_yearly", - "shazam_genre", - "soundcloud", - "soundcloud_legacy", - "soundcloud_trending", - "siriusxm", - "tencent_weekly", - "tencent_monthly", - "anghami" - ], - "optional": false, - "field": "platform", - "description": "

Platform name

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "code2", - "description": "

Country code2.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "obj", - "description": "

List of available genres.

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": [\n \"all music genres\",\n \"artist pro\",\n \"dance\",\n \"folk\",\n \"hip hop\",\n \"indie\",\n \"new & hot\",\n \"next pro\",\n \"pop\",\n \"r&b\",\n \"rock\"\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example: Genres of SoundCloud (UK)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/genres/soundcloud?code2=UK", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/:type/:type_id/:chart_type/cm-score", - "title": "Chartmetric Score", - "name": "GetChartmetricScore", - "version": "1.0.0", - "description": "

Chartmetric Score reflects the overall trend for tracks and albums.

It is computed based on the aggregate of weighted rank positions from various charting countries over time.

Note: The type_id refers to the Chartmetric type id and must match with type to give accurate results.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "album", - "artist", - "track" - ], - "optional": false, - "field": "type", - "description": "" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "type_id", - "description": "

[cm_track (Chartmetric Track ID), cm_artist (Chartmetric Artist ID), cm_album (Chartmetric Album ID)]

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify-top", - "spotify-viral", - "applemusic-genre", - "applemusic-daily", - "applemusic-albums", - "itunes", - "itunes_albums", - "shazam" - ], - "optional": false, - "field": "chart_type", - "description": "" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "since", - "description": "

Start of date range, in ISO date format (e.g. "2017-03-25").

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

End of date range, in ISO date format (e.g. "2017-03-25").

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Array[Object]", - "optional": false, - "field": "overall", - "description": "

Overall stats

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "overall.x", - "description": "

X-coordinate indicating date

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "overall.y", - "description": "

Y-coordinate indicating score on that date

" - }, - { - "group": "Success 200", - "type": "Array[Object]", - "optional": false, - "field": "overall.details", - "description": "

Details of type sepecified

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "overall.details.trackName", - "description": "

Track Name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "overall.details.trackId", - "description": "

Track ID

" - }, - { - "group": "Success 200", - "type": "Array[Object]", - "optional": false, - "field": "overall.details.details", - "description": "

Details of the track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "overall.details.details.rank", - "description": "

Rank

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "overall.details.details.country", - "description": "

Country Code

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "overall.details.details.score", - "description": "

Score at the X-coord

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "overall.count", - "description": "" - }, - { - "group": "Success 200", - "type": "Array[Object]", - "optional": false, - "field": "individual", - "description": "

Individal s

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "individual.name", - "description": "

Name of type

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "individual.id", - "description": "

ID of type

" - }, - { - "group": "Success 200", - "type": "Array[Object]", - "optional": false, - "field": "individual.data", - "description": "

Array of data objects

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "individual.data.timestamp", - "description": "

Timestamp of date of data

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "individual.data.score", - "description": "

Score represents the scaled value of change over the past month.

" - }, - { - "group": "Success 200", - "type": "Array[Object]", - "optional": false, - "field": "individual.data.rank_details", - "description": "

Rank Details

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "individual.data.rank_details.rank", - "description": "

Rank at related timestamp.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "individual.data.rank_details.country", - "description": "

Country code.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "individual.data.rank_details.score", - "description": "

Score represents the scaled value of change over the past month.

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": {\n \"overall\": [\n {\n \"x\": \"2020-07-01\",\n \"y\": 0.39,\n \"details\": [\n {\n \"trackName\": \"Woke Up Late (feat. Hailee Steinfeld)\",\n \"trackId\": 22310714,\n \"details\": [\n {\n \"rank\": 180,\n \"country\": \"AU\",\n \"score\": 0.39\n }\n ]\n }\n ],\n \"count\": 1\n },\n {\n \"x\": \"2020-07-02\",\n \"y\": 0.36,\n \"details\": [\n {\n \"trackName\": \"Woke Up Late (feat. Hailee Steinfeld)\",\n \"trackId\": 22310714,\n \"details\": [\n {\n \"rank\": 191,\n \"country\": \"AU\",\n \"score\": 0.36\n }\n ]\n }\n ],\n \"count\": 1\n }\n ],\n \"individual\": [\n {\n \"name\": \"Woke Up Late (feat. Hailee Steinfeld)\",\n \"id\": 22310714,\n \"data\": [\n {\n \"timestp\": \"2020-07-01T00:00:00.000Z\",\n \"score\": 0.39,\n \"rank_details\": [\n {\n \"rank\": 180,\n \"country\": \"AU\",\n \"score\": 0.39\n }\n ]\n },\n {\n \"timestp\": \"2020-07-02T00:00:00.000Z\",\n \"score\": 0.36,\n \"rank_details\": [\n {\n \"rank\": 191,\n \"country\": \"AU\",\n \"score\": 0.36\n }\n ]\n }\n ]\n }\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/artist/4904/spotify-top/cm-score?since=2019-02-10", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/circle/album/:chartType", - "title": "Circle (Albums)", - "name": "GetCircleAlbumsChart", - "version": "1.0.0", - "description": "

Get data insights for albums on Circle Albums Charts, including historical chart positions trend and streaming counts over time.
This endpoint is useful for identifying latest music trends in Circle Tracks Charts.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "top" - ], - "optional": false, - "field": "chartType", - "description": "

Chart type

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "description": "

Charting Date. Use /chart/dates endpoint with chartEntity as album, and corresponding chartType and duration to get available dates.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "weekly", - "monthly", - "semi-yearly", - "yearly" - ], - "optional": false, - "field": "duration", - "description": "

Chart duration

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

The limit of entries to be returned.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entityId", - "description": "

Internal system ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "targetId", - "description": "

Anghami track ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "duration", - "description": "

Chart duration

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "country", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cityId", - "description": "

City ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Chart genre

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rankDiff", - "description": "

Rank difference from previous period

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "rankTrend", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.timestp", - "description": "

Time stamp

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.rank", - "description": "

Rank at given date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "peakDate", - "description": "

Date of peak rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "peakRank", - "description": "

Top rank achieved

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "velocity7Day", - "description": "

7-day velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "periodsOnChart", - "description": "

Total periods on chart

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "consecutivePeriods", - "description": "

Consecutive periods on chart

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "chartedDate", - "description": "

Date when track first charted

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "imageUrl", - "description": "

Track image URL

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "createdAt", - "description": "

Creation timestamp

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artists", - "description": "

Artist information

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.careerStage", - "description": "

Artist career stage

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.code2", - "description": "

Artist country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.imageUrl", - "description": "

Artist image URL

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "album", - "description": "

Album information

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "album.id", - "description": "

Chartmetric album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.name", - "description": "

Album name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "album.releaseDate", - "description": "

Album release date

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "optionalRows", - "description": "

Optional additional data

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "targetExtras", - "description": "

Platform-specific metrics

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "isChartGenre", - "description": "

Whether the genre field is indicating the chart genre as opposed to the entity (track/album) genre

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": [\n {\n \"entityId\": 45348754,\n \"targetId\": \"787681\",\n \"duration\": \"weekly\",\n \"country\":\"\",\n \"cityId\": 0,\n \"rank\": 1,\n \"rankDiff\": null,\n \"rankTrend\": [\n {\n \"timestp\": \"2025-06-22\",\n \"rank\": \"1\"\n }\n ],\n \"peakDate\": \"2025-06-22\",\n \"peakRank\": 1,\n \"velocity7Day\": null,\n \"periodsOnChart\": 1,\n \"consecutivePeriods\": 1,\n \"chartedDate\": \"2025-06-22\",\n \"name\": \"Dirty Work\",\n \"imageUrl\": \"https://i.scdn.co/image/ab67616d0000b273bf35cc79c5b22b1c19065272\",\n \"createdAt\": \"2025-07-03 02:17:03\",\n \"artists\": [\n {\n \"careerStage\": \"superstar\",\n \"code2\": \"KR\",\n \"id\": 4080594,\n \"name\": \"aespa\"\n }\n ],\n \"label\": \"SM Entertainment\",\n \"numTracks\": 4,\n \"explicit\": true,\n \"releaseDate\": \"2025-06-27\",\n \"type\": \"single\",\n \"externalId\": 787681,\n \"optionalRows\": {\n \"salesVolume\": \"872842\",\n \"overallRank\": 0,\n \"videos\": 0,\n \"totalVideos\": 0,\n \"metricValue\": 0,\n \"metricDiff\": null,\n \"avgPosition\": 0,\n \"score\": 0,\n \"totalFollowers\": 0,\n \"velocity\": null,\n \"isComeback\": false,\n \"plays\": 0,\n \"playsWeekly\": 0,\n \"counts\": 0,\n \"index\": 0,\n \"comments\": 0\n },\n \"targetExtras\": {\n \"airplayStreams\": 254,\n \"amazonEdPlaylistCount\": 45,\n \"amazonPlaylistCount\": 45,\n \"deezerEdPlaylistCount\": 2,\n \"deezerEdPlaylistTotalReach\": 162830,\n \"deezerPlaylistCount\": 6,\n \"deezerPlaylistTotalReach\": 220519,\n \"itunesEdPlaylistCount\": 8,\n \"itunesPlaylistCount\": 42,\n \"spotifyEdPlaylistCount\": 37,\n \"spotifyEdPlaylistTotalReach\": 21641132,\n \"spotifyPlaylistCount\": 542,\n \"spotifyPlaylistTotalReach\": 32616504,\n \"spotifyPopularity\": 71,\n \"youtubeEdPlaylistCount\": 0,\n \"youtubeEdPlaylistTotalReach\": 0,\n \"youtubePlaylistCount\": 6,\n \"youtubePlaylistTotalReach\": 43778506\n }\n },\n {\n \"entityId\": 45269782,\n \"targetId\": \"786357\",\n \"duration\": \"weekly\",\n \"country\":\"\",\n \"cityId\": 0,\n \"rank\": 2,\n \"rankDiff\": null,\n \"rankTrend\": [\n {\n \"timestp\": \"2025-06-22\",\n \"rank\": \"2\"\n }\n ],\n \"peakDate\": \"2025-06-22\",\n \"peakRank\": 2,\n \"velocity7Day\": null,\n \"periodsOnChart\": 1,\n \"consecutivePeriods\": 1,\n \"chartedDate\": \"2025-06-22\",\n \"name\": \"Dare to Crave\",\n \"imageUrl\": \"https://i.scdn.co/image/ab67616d0000b273b6c46aa8d266c1b05040ea50\",\n \"createdAt\": \"2025-07-03 02:17:03\",\n \"artists\": [\n {\n \"careerStage\": \"mainstream\",\n \"code2\": \"KR\",\n \"id\": 3636895,\n \"name\": \"CRAVITY\"\n }\n ],\n \"label\": \"Starship Entertainment\",\n \"numTracks\": 12,\n \"explicit\": false,\n \"releaseDate\": \"2025-06-23\",\n \"type\": \"album\",\n \"externalId\": 786357,\n \"optionalRows\": {\n \"salesVolume\": \"252152\",\n \"overallRank\": 0,\n \"videos\": 0,\n \"totalVideos\": 0,\n \"metricValue\": 0,\n \"metricDiff\": null,\n \"avgPosition\": 0,\n \"score\": 0,\n \"totalFollowers\": 0,\n \"velocity\": null,\n \"isComeback\": false,\n \"plays\": 0,\n \"playsWeekly\": 0,\n \"counts\": 0,\n \"index\": 0,\n \"comments\": 0\n },\n \"targetExtras\": {\n \"airplayStreams\": 38,\n \"itunesEdPlaylistCount\": 4,\n \"itunesPlaylistCount\": 5,\n \"spotifyEdPlaylistCount\": 3,\n \"spotifyEdPlaylistTotalReach\": 1241374,\n \"spotifyPlaylistCount\": 63,\n \"spotifyPlaylistTotalReach\": 2132505,\n \"spotifyPopularity\": 54\n }\n },\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/circle/album/top?duration=weekly&date=2025-06-22", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/circle/track/:chartType", - "title": "Circle (Tracks)", - "name": "GetCircleTracksChart", - "version": "1.0.0", - "description": "

Get data insights for tracks on Circle Tracks Charts, including historical chart positions trend and streaming counts over time.
This endpoint is useful for identifying latest music trends in Circle Tracks Charts.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "global_k_pop" - ], - "optional": false, - "field": "chartType", - "description": "

Chart type

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "description": "

Charting Date. Use /chart/dates endpoint with chartEntity as track, and corresponding chartType and duration to get available dates.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "daily", - "weekly", - "monthly", - "semi-yearly", - "yearly" - ], - "optional": false, - "field": "duration", - "description": "

Chart duration

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

The limit of entries to be returned.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entityId", - "description": "

Internal system ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "targetId", - "description": "

Anghami track ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "duration", - "description": "

Chart duration

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "country", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cityId", - "description": "

City ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Chart genre

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rankDiff", - "description": "

Rank difference from previous period

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "rankTrend", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.timestp", - "description": "

Time stamp

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.rank", - "description": "

Rank at given date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "peakDate", - "description": "

Date of peak rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "peakRank", - "description": "

Top rank achieved

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "velocity7Day", - "description": "

7-day velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "periodsOnChart", - "description": "

Total periods on chart

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "consecutivePeriods", - "description": "

Consecutive periods on chart

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "chartedDate", - "description": "

Date when track first charted

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "imageUrl", - "description": "

Track image URL

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "createdAt", - "description": "

Creation timestamp

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artists", - "description": "

Artist information

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.careerStage", - "description": "

Artist career stage

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.code2", - "description": "

Artist country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.imageUrl", - "description": "

Artist image URL

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "album", - "description": "

Album information

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "album.id", - "description": "

Chartmetric album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.name", - "description": "

Album name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "album.releaseDate", - "description": "

Album release date

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "optionalRows", - "description": "

Optional additional data

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "targetExtras", - "description": "

Platform-specific metrics

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "isChartGenre", - "description": "

Whether the genre field is indicating the chart genre as opposed to the entity (track/album) genre

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": [\n {\n \"entityId\": 138071658,\n \"targetId\": \"141653\",\n \"duration\": \"weekly\",\n \"country\":\"\",\n \"cityId\": 0,\n \"genre\": \"pop, k-pop, dance\",\n \"rank\": 1,\n \"rankDiff\": 0,\n \"rankTrend\": [\n {\n \"timestp\": \"2025-03-16\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-03-23\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-03-30\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-04-06\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-04-13\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-04-20\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-04-27\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-05-04\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-05-11\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-05-18\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-05-25\",\n \"rank\": \"1\"\n }\n ],\n \"peakDate\": \"2024-10-20\",\n \"peakRank\": 1,\n \"velocity7Day\": 0,\n \"periodsOnChart\": 33,\n \"consecutivePeriods\": 33,\n \"chartedDate\": \"2025-05-25\",\n \"name\": \"APT.\",\n \"imageUrl\": \"https://i.scdn.co/image/ab67616d0000b27336032cb4acd9df050bc2e197\",\n \"createdAt\": \"2025-06-12 01:29:31\",\n \"artists\": [\n {\n \"careerStage\": \"superstar\",\n \"code2\": \"KR\",\n \"id\": 4114519,\n \"name\": \"ROSÉ\",\n \"imageUrl\": \"https://i.scdn.co/image/ab6761610000e5ebcfb4350222919670128ff2dc\"\n },\n {\n \"careerStage\": \"superstar\",\n \"code2\": \"US\",\n \"id\": 3501,\n \"name\": \"Bruno Mars\",\n \"imageUrl\": \"https://i.scdn.co/image/ab6761610000e5ebc36dd9eb55fb0db4911f25dd\"\n }\n ],\n \"album\": {\n \"id\": 39667137,\n \"name\": \"APT.\",\n \"label\": \"Atlantic Records\",\n \"releaseDate\": \"2024-10-18T00:00:00.000Z\"\n },\n \"externalId\": 141653,\n \"optionalRows\": {\n \"overallRank\": 0,\n \"videos\": 0,\n \"totalVideos\": 0,\n \"metricValue\": 0,\n \"metricDiff\": null,\n \"avgPosition\": 0,\n \"score\": 0,\n \"totalFollowers\": 0,\n \"velocity\": null,\n \"isComeback\": false,\n \"plays\": 0,\n \"playsWeekly\": 0,\n \"counts\": 0,\n \"index\": 0,\n \"comments\": 0\n },\n \"targetExtras\": {\n \"airplayStreams\": 633819,\n \"anghamiLikes\": 51092,\n \"anghamiPlays\": 2104993,\n \"deezerEdPlaylistCount\": 20,\n \"deezerEdPlaylistTotalReach\": 10683822,\n \"deezerPlaylistCount\": 168,\n \"deezerPlaylistTotalReach\": 36970763,\n \"itunesEdPlaylistCount\": 5,\n \"itunesPlaylistCount\": 7,\n \"lineMusicLikes\": 157599,\n \"lineMusicListenCount\": 21324176,\n \"melonComments\": 2614,\n \"melonLikes\": 214206,\n \"pandoraLifetimeStationsAdded\": 72871,\n \"pandoraLifetimeStreams\": 35878952,\n \"siriusxmStreams\": 972,\n \"soundcloudPlays\": 2980988,\n \"spotifyEdPlaylistCount\": 130,\n \"spotifyEdPlaylistTotalReach\": 78591976,\n \"spotifyPlaylistCount\": 185139,\n \"spotifyPlaylistTotalReach\": 245998482,\n \"spotifyPlays\": 1807815878,\n \"spotifyPopularity\": 94,\n \"tiktokTopVideosComments\": 9207612,\n \"tiktokTopVideosLikes\": 1064367067,\n \"tiktokTopVideosViews\": 14866451787,\n \"youtubeComments\": 750551,\n \"youtubeEdPlaylistCount\": 0,\n \"youtubeEdPlaylistTotalReach\": 0,\n \"youtubeLikes\": 16004356,\n \"youtubePlaylistCount\": 335,\n \"youtubePlaylistTotalReach\": 12386375836,\n \"youtubeViews\": 1711954091\n },\n \"isChartGenre\": false,\n \"id\": 141653,\n \"platform_id\": 141653,\n \"artistName\": \"로제 (ROSE), Bruno Mars\"\n },\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/circle/track/global_k_pop?duration=weekly&date=2025-05-25", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/deezer/", - "title": "Deezer", - "name": "GetDeezerChart", - "version": "1.0.0", - "description": "

Get data insights for the top tracks on Deezer for a given day in a certain country

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "US", - "JP", - "..." - ], - "optional": false, - "field": "country_code", - "description": "

Country Code. (Full list in Introduction)

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Charting Date

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Number of data entries.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

List of results.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.id", - "description": "

Deezer track id.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

Track name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.isrc", - "description": "

Track isrc.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.deezer_album", - "description": "

Deezer album id.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.image_url", - "description": "

image url.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.cm_track", - "description": "

chartmetric track id.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.deezer_artist_names", - "description": "

Deezer artist names.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.cm_artist", - "description": "

Chartmetric artist id.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_names", - "description": "

Artist names.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.code2s", - "description": "

Country codes.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_images", - "description": "

Artist images.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_covers", - "description": "

Artist covers.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.deezer_artist_ids", - "description": "

Deezer artist ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.deezer_track_ids", - "description": "

Deezer track ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.deezer_album_ids", - "description": "

Deezer album ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_ids", - "description": "

Album ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_names", - "description": "

Album names.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_upc", - "description": "

Album upc.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_label", - "description": "

Album label.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.release_dates", - "description": "

Release dates.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.added_at", - "description": "

Added date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.code2", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.deezer", - "description": "

Deezer track id

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.velocity", - "description": "

Velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.pre_rank", - "description": "

Previous rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.peak_rank", - "description": "

Top rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.peak_date", - "description": "

Date of top rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.time_on_chart", - "description": "

Track time on chart

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.rankStats", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rankStats.rank", - "description": "

Rank of the track

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.rankStats.timestp", - "description": "

Timestamp of the rank

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": {\n \"length\": 100,\n \"data\": [\n {\n \"id\": \"1078759092\",\n \"name\": \"Mi Niña\",\n \"isrc\": \"QMBZ92014731\",\n \"deezer_album\": 172939202,\n \"image_url\": \"https://api.deezer.com/album/172939202/image\",\n \"cm_track\": 31444727,\n \"deezer_artist_names\": [\n \"Myke Towers\",\n \"Wisin\",\n \"Los Legendarios\"\n ],\n \"cm_artist\": [\n 493823,\n 206685,\n 716166\n ],\n \"artist_names\": [\n \"Myke Towers\",\n \"Wisin\",\n \"Los Legendarios\"\n ],\n \"code2s\": [\n \"PR\",\n \"PR\",\n null\n ],\n \"artist_images\": [\n \"https://api.deezer.com/artist/12029862/image\",\n \"https://api.deezer.com/artist/15898/image\",\n \"https://api.deezer.com/artist/6611456/image\"\n ],\n \"artist_covers\": [\n null,\n null,\n null\n ],\n \"deezer_artist_ids\": [\n \"12029862\",\n \"15898\",\n \"6611456\"\n ],\n \"deezer_track_ids\": [\n \"1078759092\"\n ],\n \"deezer_album_ids\": [\n \"172939202\"\n ],\n \"album_ids\": [\n 6778811\n ],\n \"album_names\": [\n \"Mi Niña\"\n ],\n \"album_upc\": [\n \"195497230747\"\n ],\n \"album_label\": [\n \"La Base Music Group/ WK Records\"\n ],\n \"release_dates\": [\n \"2020-09-23T00:00:00.000Z\"\n ],\n \"rank\": 100,\n \"added_at\": \"2020-12-12T00:00:00.000Z\",\n \"code2\": \"US \",\n \"deezer\": \"1078759092\",\n \"velocity\": null,\n \"pre_rank\": 91,\n \"peak_rank\": 50,\n \"peak_date\": \"2020-11-21T00:00:00.000Z\",\n \"time_on_chart\": \"24\",\n \"rankStats\": [\n {\n \"rank\": 91,\n \"timestp\": \"2020-12-09T00:00:00.000Z\"\n }, ...\n ]\n }\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/deezer?country_code=US&date=2019-3-18", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/hanteo/album/:chartType", - "title": "Hanteo (Albums)", - "name": "GetHanteoAlbumsChart", - "version": "1.0.0", - "description": "

Get data insights for albums on Hanteo Albums Charts, including historical chart positions trend and streaming counts over time.
This endpoint is useful for identifying latest music trends in Korea on Hanteo Charts.

chartType code2 duration
top does not support code2 daily, weekly, monthly, yearly
world US, KR, JP does not support duration
music does not support code2 daily, weekly, monthly
", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "top", - "music", - "world" - ], - "optional": false, - "field": "chartType", - "description": "

Chart type

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "description": "

Charting Date. Use /chart/dates endpoint with chartEntity as album, and corresponding chartType and duration to get available dates.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "code2", - "description": "

Country code filter. See above table for available countries for each chart type.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "duration", - "description": "

Chart Interval. See below table for available durations for each chart type.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

The limit of entries to be returned.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entityId", - "description": "

Internal system ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "targetId", - "description": "

Melon track ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "duration", - "description": "

Chart duration

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "country", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cityId", - "description": "

City ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Chart genre

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rankDiff", - "description": "

Rank difference from previous period

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "rankTrend", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.timestp", - "description": "

Time stamp

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.rank", - "description": "

Rank at given date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "peakDate", - "description": "

Date of peak rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "peakRank", - "description": "

Top rank achieved

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "velocity7Day", - "description": "

7-day velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "periodsOnChart", - "description": "

Total periods on chart

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "consecutivePeriods", - "description": "

Consecutive periods on chart

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "chartedDate", - "description": "

Date when track first charted

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "imageUrl", - "description": "

Track image URL

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "createdAt", - "description": "

Creation timestamp

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artists", - "description": "

Artist information

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.careerStage", - "description": "

Artist career stage

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.code2", - "description": "

Artist country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.imageUrl", - "description": "

Artist image URL

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "album", - "description": "

Album information

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "album.id", - "description": "

Chartmetric album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.name", - "description": "

Album name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "album.releaseDate", - "description": "

Album release date

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "optionalRows", - "description": "

Optional additional data

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "targetExtras", - "description": "

Platform-specific metrics

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "isChartGenre", - "description": "

Whether the genre field is indicating the chart genre as opposed to the entity (track/album) genre

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": [\n {\n \"entityId\": 44704618,\n \"targetId\": \"13256\",\n \"duration\": \"daily\",\n \"country\":\"\",\n \"cityId\": 0,\n \"rank\": 1,\n \"rankDiff\": 10,\n \"rankTrend\": [\n {\n \"timestp\": \"2025-05-31\",\n \"rank\": \"3\"\n },\n {\n \"timestp\": \"2025-06-01\",\n \"rank\": \"79\"\n },\n {\n \"timestp\": \"2025-06-02\",\n \"rank\": \"11\"\n },\n {\n \"timestp\": \"2025-06-03\",\n \"rank\": \"1\"\n }\n ],\n \"peakDate\": \"2025-06-03\",\n \"peakRank\": 1,\n \"velocity7Day\": null,\n \"periodsOnChart\": 7,\n \"consecutivePeriods\": 7,\n \"chartedDate\": \"2025-06-03\",\n \"name\": \"Everlasting\",\n \"imageUrl\": \"https://hanteochart.com/album/900544205_s150.jpg\",\n \"createdAt\": \"2025-06-09 02:08:47\",\n \"artists\": [\n {}\n ],\n \"label\": \"FNC\",\n \"releaseDate\": \"2025-05-28\",\n \"externalId\": 900544205,\n \"optionalRows\": {\n \"physicalRecordIndex\": \"7354.333\",\n \"label\": \"FNC\",\n \"overallRank\": 0,\n \"videos\": 0,\n \"totalVideos\": 0,\n \"metricValue\": 0,\n \"metricDiff\": null,\n \"avgPosition\": 0,\n \"score\": 0,\n \"totalFollowers\": 0,\n \"velocity\": null,\n \"isComeback\": false,\n \"plays\": 0,\n \"playsWeekly\": 0,\n \"counts\": 0,\n \"index\": 0,\n \"comments\": 0\n },\n \"targetExtras\": {}\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/hanteo/album/music?duration=daily&date=2025-06-03", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/hanteo/track/:chartType", - "title": "Hanteo (Tracks)", - "name": "GetHanteoTracksChart", - "version": "1.0.0", - "description": "

Get data insights for tracks on Hanteo Tracks Charts, including historical chart positions trend and streaming counts over time.
This endpoint is useful for identifying latest music trends in Korea on Hanteo Charts.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "digital" - ], - "optional": false, - "field": "chartType", - "description": "

Chart type

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "description": "

Charting Date. Use /chart/dates endpoint with chartEntity as track, and corresponding chartType and duration to get available dates.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "daily", - "weekly", - "monthly" - ], - "optional": false, - "field": "duration", - "description": "

Chart Interval

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

The limit of entries to be returned.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entityId", - "description": "

Internal system ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "targetId", - "description": "

Melon track ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "duration", - "description": "

Chart duration

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "country", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cityId", - "description": "

City ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Chart genre

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rankDiff", - "description": "

Rank difference from previous period

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "rankTrend", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.timestp", - "description": "

Time stamp

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.rank", - "description": "

Rank at given date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "peakDate", - "description": "

Date of peak rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "peakRank", - "description": "

Top rank achieved

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "velocity7Day", - "description": "

7-day velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "periodsOnChart", - "description": "

Total periods on chart

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "consecutivePeriods", - "description": "

Consecutive periods on chart

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "chartedDate", - "description": "

Date when track first charted

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "imageUrl", - "description": "

Track image URL

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "createdAt", - "description": "

Creation timestamp

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artists", - "description": "

Artist information

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.careerStage", - "description": "

Artist career stage

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.code2", - "description": "

Artist country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.imageUrl", - "description": "

Artist image URL

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "album", - "description": "

Album information

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "album.id", - "description": "

Chartmetric album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.name", - "description": "

Album name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "album.releaseDate", - "description": "

Album release date

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "optionalRows", - "description": "

Optional additional data

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "targetExtras", - "description": "

Platform-specific metrics

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "isChartGenre", - "description": "

Whether the genre field is indicating the chart genre as opposed to the entity (track/album) genre

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": [\n {\n \"entityId\": 144447279,\n \"targetId\": \"78\",\n \"duration\": \"daily\",\n \"country\":\"\",\n \"cityId\": 0,\n \"genre\": \"rock, pop, k-pop\",\n \"rank\": 1,\n \"rankDiff\": 0,\n \"rankTrend\": [\n {\n \"timestp\": \"2025-05-31\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-06-01\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-06-02\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-06-03\",\n \"rank\": \"1\"\n }\n ],\n \"peakDate\": \"2025-05-11\",\n \"peakRank\": 1,\n \"velocity7Day\": 0,\n \"periodsOnChart\": 46,\n \"consecutivePeriods\": 46,\n \"chartedDate\": \"2025-06-03\",\n \"name\": \"너에게 닿기를\",\n \"imageUrl\": \"https://i.scdn.co/image/ab67616d0000b27358c6956da5fa380b67986551\",\n \"createdAt\": \"2025-06-09 02:20:15\",\n \"artists\": [\n {\n \"careerStage\": \"mainstream\",\n \"code2\": \"KR\",\n \"id\": 217578,\n \"name\": \"10CM\",\n \"imageUrl\": \"https://i.scdn.co/image/ab6761610000e5eb2445af13cb1a8132388cd566\"\n }\n ],\n \"album\": {\n \"id\": 42895219,\n \"name\": \"To Reach You\",\n \"label\": \"CAM\",\n \"releaseDate\": \"2025-03-06T00:00:00.000Z\"\n },\n \"externalId\": 1830886,\n \"optionalRows\": {\n \"physicalRecordIndex\": \"9963.93\",\n \"label\": \"CAM\",\n \"overallRank\": 0,\n \"videos\": 0,\n \"totalVideos\": 0,\n \"metricValue\": 0,\n \"metricDiff\": null,\n \"avgPosition\": 0,\n \"score\": 0,\n \"totalFollowers\": 0,\n \"velocity\": null,\n \"isComeback\": false,\n \"plays\": 0,\n \"playsWeekly\": 0,\n \"counts\": 0,\n \"index\": 0,\n \"comments\": 0\n },\n \"targetExtras\": {\n \"melonComments\": 698,\n \"melonLikes\": 114826,\n \"pandoraLifetimeStreams\": 443,\n \"shazamCount\": 42482,\n \"spotifyEdPlaylistCount\": 0,\n \"spotifyEdPlaylistTotalReach\": 0,\n \"spotifyPlaylistCount\": 10,\n \"spotifyPlaylistTotalReach\": 434,\n \"spotifyPlays\": 52686,\n \"spotifyPopularity\": 30\n },\n \"isChartGenre\": false,\n \"id\": 78,\n \"platform_id\": 1830886\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/hanteo/track/digital?duration=daily&date=2025-06-03", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/line_music/album/:chartType", - "title": "Line Music (Albums)", - "name": "GetLineMusicAlbumsChart", - "version": "1.0.0", - "description": "

Get data insights for albums on Line Music Albums Charts, including historical chart positions trend and streaming counts over time.
This endpoint is useful for identifying latest music trends in Japan on Line Music Albums Charts.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "top" - ], - "optional": false, - "field": "chartType", - "description": "

Chart type

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "description": "

Charting Date. Use /chart/dates endpoint to get available dates. Available dates are supported by line_{duration}_albums streamingType.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "daily", - "weekly" - ], - "optional": false, - "field": "duration", - "description": "

Chart Interval

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

The limit of entries to be returned.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entityId", - "description": "

Internal system ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "targetId", - "description": "

Line Music album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "duration", - "description": "

Chart duration

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "country", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cityId", - "description": "

City ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Chart genre

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rankDiff", - "description": "

Rank difference from previous period

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "rankTrend", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.timestp", - "description": "

Time stamp

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.rank", - "description": "

Rank at given date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "peakDate", - "description": "

Date of peak rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "peakRank", - "description": "

Top rank achieved

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "velocity7Day", - "description": "

7-day velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "periodsOnChart", - "description": "

Total periods on chart

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "consecutivePeriods", - "description": "

Consecutive periods on chart

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "chartedDate", - "description": "

Date when track first charted

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "imageUrl", - "description": "

Track image URL

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "createdAt", - "description": "

Creation timestamp

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artists", - "description": "

Artist information

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.careerStage", - "description": "

Artist career stage

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.code2", - "description": "

Artist country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.imageUrl", - "description": "

Artist image URL

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "album", - "description": "

Album information

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "album.id", - "description": "

Chartmetric album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.name", - "description": "

Album name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "album.releaseDate", - "description": "

Album release date

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "optionalRows", - "description": "

Optional additional data

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "targetExtras", - "description": "

Platform-specific metrics

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "isChartGenre", - "description": "

Whether the genre field is indicating the chart genre as opposed to the entity (track/album) genre

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": [\n {\n \"entityId\": 0,\n \"targetId\": \"mb000000000446ed9e\",\n \"duration\": \"daily\",\n \"country\":\"\",\n \"cityId\": 0,\n \"rank\": 1,\n \"rankDiff\": 0,\n \"rankTrend\": [\n {\n \"timestp\": \"2025-05-31\",\n \"rank\": \"2\"\n },\n {\n \"timestp\": \"2025-06-01\",\n \"rank\": \"2\"\n },\n {\n \"timestp\": \"2025-06-02\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-06-03\",\n \"rank\": \"1\"\n }\n ],\n \"peakDate\": \"2025-04-07\",\n \"peakRank\": 1,\n \"velocity7Day\": 0.2857143,\n \"periodsOnChart\": 58,\n \"consecutivePeriods\": 58,\n \"chartedDate\": \"2025-06-03\",\n \"createdAt\": \"2025-06-09 02:16:55\",\n \"artists\": [\n {}\n ],\n \"optionalRows\": {\n \"overallRank\": 0,\n \"videos\": 0,\n \"totalVideos\": 0,\n \"metricValue\": 0,\n \"metricDiff\": null,\n \"avgPosition\": 0,\n \"score\": 0,\n \"totalFollowers\": 0,\n \"velocity\": null,\n \"isComeback\": false,\n \"plays\": 0,\n \"playsWeekly\": 0,\n \"counts\": 0,\n \"index\": 0,\n \"comments\": 0\n },\n \"targetExtras\": null\n },\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/line_music/album/top?duration=daily&date=2025-06-03", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/line_music/track/:chartType", - "title": "Line Music (Tracks)", - "name": "GetLineMusicTracksChart", - "version": "1.0.0", - "description": "

Get data insights for tracks on Line Music Tracks Charts, including historical chart positions trend and streaming counts over time.
This endpoint is useful for identifying latest music trends in Japan on Line Music Charts.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "top" - ], - "optional": false, - "field": "chartType", - "description": "

Chart type

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "description": "

Charting Date. Use /chart/dates endpoint to get available dates. Available dates are supported by line_{duration} streamingType.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "daily", - "weekly" - ], - "optional": false, - "field": "duration", - "description": "

Chart Interval

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "anime", - "classic", - "dance", - "edm", - "hip", - "hop", - "japanese", - "music", - "japanese", - "music", - "rock", - "jazz", - "kayokyoku/enka", - "kids", - "k-pop", - "pop", - "r&b", - "reggae", - "rock", - "top", - "100", - "vocaloid", - "western", - "music", - "world", - "music" - ], - "optional": false, - "field": "genre", - "description": "

Genre filter.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

The limit of entries to be returned.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entityId", - "description": "

Internal system ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "targetId", - "description": "

Melon track ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "duration", - "description": "

Chart duration

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "country", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cityId", - "description": "

City ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Chart genre

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rankDiff", - "description": "

Rank difference from previous period

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "rankTrend", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.timestp", - "description": "

Time stamp

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.rank", - "description": "

Rank at given date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "peakDate", - "description": "

Date of peak rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "peakRank", - "description": "

Top rank achieved

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "velocity7Day", - "description": "

7-day velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "periodsOnChart", - "description": "

Total periods on chart

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "consecutivePeriods", - "description": "

Consecutive periods on chart

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "chartedDate", - "description": "

Date when track first charted

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "imageUrl", - "description": "

Track image URL

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "createdAt", - "description": "

Creation timestamp

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artists", - "description": "

Artist information

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.careerStage", - "description": "

Artist career stage

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.code2", - "description": "

Artist country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.imageUrl", - "description": "

Artist image URL

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "album", - "description": "

Album information

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "album.id", - "description": "

Chartmetric album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.name", - "description": "

Album name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "album.releaseDate", - "description": "

Album release date

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "optionalRows", - "description": "

Optional additional data

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "targetExtras", - "description": "

Platform-specific metrics

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "isChartGenre", - "description": "

Whether the genre field is indicating the chart genre as opposed to the entity (track/album) genre

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": [\n {\n \"entityId\": 142483816,\n \"targetId\": \"mt0000000020d08f07\",\n \"duration\": \"daily\",\n \"country\":\"\",\n \"cityId\": 0,\n \"genre\": \"Anime\",\n \"rank\": 1,\n \"rankDiff\": 0,\n \"rankTrend\": [\n {\n \"timestp\": \"2025-05-31\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-06-01\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-06-02\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-06-03\",\n \"rank\": \"1\"\n }\n ],\n \"peakDate\": \"2025-04-06\",\n \"peakRank\": 1,\n \"velocity7Day\": 0,\n \"periodsOnChart\": 76,\n \"consecutivePeriods\": 76,\n \"chartedDate\": \"2025-06-03\",\n \"name\": \"愛♡スクリ~ム!\",\n \"imageUrl\": \"https://i.scdn.co/image/ab67616d0000b2734840f84e07ee7e34a6414896\",\n \"createdAt\": \"2025-06-09 02:06:42\",\n \"artists\": [\n {\n \"careerStage\": \"mainstream\",\n \"code2\": \"JP\",\n \"id\": 13330465,\n \"name\": \"AiScReam\",\n \"imageUrl\": \"https://i.scdn.co/image/ab6761610000e5ebda8783293aa77cd94d11f8b7\"\n }\n ],\n \"album\": {\n \"id\": 44252905,\n \"name\": \"愛♡スクリ~ム!\",\n \"label\": \"Lantis\",\n \"releaseDate\": \"2025-01-21T00:00:00.000Z\"\n },\n \"optionalRows\": {\n \"overallRank\": 0,\n \"videos\": 0,\n \"totalVideos\": 0,\n \"metricValue\": 0,\n \"metricDiff\": null,\n \"avgPosition\": 0,\n \"score\": 0,\n \"totalFollowers\": 0,\n \"velocity\": null,\n \"isComeback\": false,\n \"plays\": 0,\n \"playsWeekly\": 0,\n \"counts\": 0,\n \"index\": 0,\n \"comments\": 0\n },\n \"targetExtras\": {\n \"airplayStreams\": 61,\n \"amazonEdPlaylistCount\": 1,\n \"amazonPlaylistCount\": 1,\n \"anghamiLikes\": 221,\n \"anghamiPlays\": 4274,\n \"itunesEdPlaylistCount\": 2,\n \"itunesPlaylistCount\": 7,\n \"lineMusicLikes\": 20884,\n \"lineMusicListenCount\": 1334992,\n \"shazamCount\": 299880,\n \"soundcloudPlays\": 328164,\n \"spotifyEdPlaylistCount\": 10,\n \"spotifyEdPlaylistTotalReach\": 3240984,\n \"spotifyPlaylistCount\": 3222,\n \"spotifyPlaylistTotalReach\": 4391073,\n \"spotifyPlays\": 19630150,\n \"spotifyPopularity\": 75,\n \"tiktokTopVideosComments\": 2061768,\n \"tiktokTopVideosLikes\": 229337760,\n \"tiktokTopVideosViews\": 2097796893,\n \"youtubeComments\": 26090,\n \"youtubeEdPlaylistCount\": 0,\n \"youtubeEdPlaylistTotalReach\": 0,\n \"youtubeLikes\": 989864,\n \"youtubePlaylistCount\": 9,\n \"youtubePlaylistTotalReach\": 1190750937,\n \"youtubeViews\": 53628937\n },\n \"isChartGenre\": true\n },\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/line_music/track/top?duration=daily&genre=anime&date=2025-06-03", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/melon/track/:chartType", - "title": "Melon (Tracks)", - "name": "GetMelonTracksChart", - "version": "1.0.0", - "description": "

Get data insights for tracks on Melon Tracks Charts, including historical chart positions trend and streaming counts over time.
This endpoint is useful for identifying latest music trends in Korea on Melon Charts.

chartType genre duration
general All Genres, All Pop, All K-Pop daily, weekly, monthly, yearly
k-pop Ballad, Dance, Folk/Blues, Indie Music, Rap/Hip-Hop, R&B/Soul, Rock/Metal, Trot daily, weekly, monthly
pop Electronic, Folk/Blues/Country, Pop, Rap/Hip-Hop, R&B/Soul, Rock/Metal daily, weekly, monthly
others CCM, Child/Prenatal, Gugak, Jazz, J-Pop, New Age, OST, Religious Music, World Music daily, weekly, monthly

Note that top and hot are single chart and does not support filters.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "general", - "pop", - "k-pop", - "others", - "top", - "hot" - ], - "optional": false, - "field": "chartType", - "description": "

Chart type

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "description": "

Charting Date. Use /chart/dates endpoint to get available dates. Available dates for top and hot charts are supported by melon streamingType, and all other charts are supported by melon_{duration} streamingType.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "duration", - "description": "

Chart Interval. See below table for available durations for each chart type.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Genre filter. See above table for available genres for each chart type.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

The limit of entries to be returned.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entityId", - "description": "

Internal system ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "targetId", - "description": "

Melon track ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "duration", - "description": "

Chart duration

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "country", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cityId", - "description": "

City ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Chart genre

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rankDiff", - "description": "

Rank difference from previous period

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "rankTrend", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.timestp", - "description": "

Time stamp

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.rank", - "description": "

Rank at given date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "peakDate", - "description": "

Date of peak rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "peakRank", - "description": "

Top rank achieved

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "velocity7Day", - "description": "

7-day velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "periodsOnChart", - "description": "

Total periods on chart

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "consecutivePeriods", - "description": "

Consecutive periods on chart

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "chartedDate", - "description": "

Date when track first charted

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "imageUrl", - "description": "

Track image URL

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "createdAt", - "description": "

Creation timestamp

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artists", - "description": "

Artist information

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.careerStage", - "description": "

Artist career stage

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.code2", - "description": "

Artist country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.imageUrl", - "description": "

Artist image URL

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "album", - "description": "

Album information

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "album.id", - "description": "

Chartmetric album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.name", - "description": "

Album name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "album.releaseDate", - "description": "

Album release date

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "optionalRows", - "description": "

Optional additional data

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "targetExtras", - "description": "

Platform-specific metrics

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "isChartGenre", - "description": "

Whether the genre field is indicating the chart genre as opposed to the entity (track/album) genre

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": [\n {\n \"entityId\": 144447279,\n \"targetId\": \"38626852\",\n \"duration\": \"daily\",\n \"country\": \"\",\n \"cityId\": 0,\n \"genre\": \"All Genres\",\n \"rank\": 1,\n \"rankDiff\": 0,\n \"rankTrend\": [\n {\n \"timestp\": \"2025-05-28\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-05-29\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-05-30\",\n \"rank\": \"1\"\n }\n ],\n \"peakDate\": \"2025-05-11\",\n \"peakRank\": 1,\n \"velocity7Day\": 0,\n \"periodsOnChart\": 85,\n \"consecutivePeriods\": 85,\n \"chartedDate\": \"2025-05-30\",\n \"name\": \"To Reach You (Inst.)\",\n \"imageUrl\": \"https://i.scdn.co/image/ab67616d0000b27358c6956da5fa380b67986551\",\n \"createdAt\": \"2025-06-03 07:24:14\",\n \"artists\": [\n {\n \"careerStage\": \"mainstream\",\n \"code2\": \"KR\",\n \"id\": 217578,\n \"name\": \"10CM\",\n \"imageUrl\": \"https://i.scdn.co/image/ab6761610000e5eb2445af13cb1a8132388cd566\"\n }\n ],\n \"album\": {\n \"id\": 42895219,\n \"name\": \"To Reach You\",\n \"label\": \"CAM\",\n \"releaseDate\": \"2025-03-06T00:00:00.000Z\"\n },\n \"optionalRows\": {\n \"overallRank\": 0,\n \"videos\": 0,\n \"totalVideos\": 0,\n \"metricValue\": 0,\n \"metricDiff\": null,\n \"avgPosition\": 0,\n \"score\": 0,\n \"totalFollowers\": 0,\n \"velocity\": null,\n \"isComeback\": false,\n \"plays\": 0,\n \"playsWeekly\": 0,\n \"counts\": 0,\n \"index\": 0,\n \"comments\": 0\n },\n \"targetExtras\": {\n \"melonComments\": 698,\n \"melonLikes\": 114826,\n \"pandoraLifetimeStreams\": 443,\n \"shazamCount\": 42482,\n \"spotifyEdPlaylistCount\": 0,\n \"spotifyEdPlaylistTotalReach\": 0,\n \"spotifyPlaylistCount\": 10,\n \"spotifyPlaylistTotalReach\": 434,\n \"spotifyPlays\": 52686,\n \"spotifyPopularity\": 30\n },\n \"isChartGenre\": true\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/melon/track/general?duration=daily&date=2025-06-01&genre=All Genres", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/pandora/track/:chartType", - "title": "Pandora (Tracks)", - "name": "GetPandoraTracksChart", - "version": "1.0.0", - "description": "

Get data insights for tracks on Pandora Tracks Charts, including historical chart positions trend and streaming counts over time.
This endpoint is useful for identifying latest music trends in Pandora Tracks Charts.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "top_spins", - "top_thumb_hundred" - ], - "optional": false, - "field": "chartType", - "description": "

Chart type

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "description": "

Charting Date. Use /chart/dates endpoint to get available dates. Available dates are supported by pandora_top_spins and pandora_top_thumbs streamingType.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

The limit of entries to be returned.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "entityId", - "description": "

Internal system ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "targetId", - "description": "

Line Music album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "duration", - "description": "

Chart duration

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "country", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cityId", - "description": "

City ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Chart genre

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rankDiff", - "description": "

Rank difference from previous period

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "rankTrend", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.timestp", - "description": "

Time stamp

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.rank", - "description": "

Rank at given date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "peakDate", - "description": "

Date of peak rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "peakRank", - "description": "

Top rank achieved

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "velocity7Day", - "description": "

7-day velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "periodsOnChart", - "description": "

Total periods on chart

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "consecutivePeriods", - "description": "

Consecutive periods on chart

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "chartedDate", - "description": "

Date when track first charted

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "imageUrl", - "description": "

Track image URL

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "createdAt", - "description": "

Creation timestamp

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artists", - "description": "

Artist information

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.careerStage", - "description": "

Artist career stage

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.code2", - "description": "

Artist country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.imageUrl", - "description": "

Artist image URL

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "album", - "description": "

Album information

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "album.id", - "description": "

Chartmetric album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.name", - "description": "

Album name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "album.releaseDate", - "description": "

Album release date

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "optionalRows", - "description": "

Optional additional data

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "targetExtras", - "description": "

Platform-specific metrics

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "isChartGenre", - "description": "

Whether the genre field is indicating the chart genre as opposed to the entity (track/album) genre

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": [\n {\n \"entityId\": 142829508,\n \"targetId\": \"S159745474\",\n \"country\":\"\",\n \"cityId\": 0,\n \"genre\": \"country, alternative, pop\",\n \"rank\": 1,\n \"rankDiff\": 0,\n \"rankTrend\": [\n {\n \"timestp\": \"2025-06-05\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-06-12\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-06-19\",\n \"rank\": \"1\"\n },\n {\n \"timestp\": \"2025-06-26\",\n \"rank\": \"1\"\n }\n ],\n \"peakDate\": \"2025-05-29\",\n \"peakRank\": 1,\n \"velocity7Day\": 0,\n \"periodsOnChart\": 22,\n \"consecutivePeriods\": 5,\n \"chartedDate\": \"2025-06-26\",\n \"name\": \"I'm The Problem\",\n \"imageUrl\": \"https://i.scdn.co/image/ab67616d0000b273985da65c010002691e5ba05a\",\n \"createdAt\": \"2025-07-04 20:12:44\",\n \"artists\": [\n {\n \"careerStage\": \"superstar\",\n \"code2\": \"US\",\n \"id\": 212715,\n \"name\": \"Morgan Wallen\",\n \"imageUrl\": \"https://i.scdn.co/image/ab6761610000e5eb4245b1652fcc23f2b76ccd07\"\n }\n ],\n \"album\": {\n \"id\": 42059519,\n \"name\": \"I'm The Problem\",\n \"label\": \"Big Loud Records / Mercury Records / Republic Records\",\n \"releaseDate\": \"2025-01-31T00:00:00.000Z\"\n },\n \"optionalRows\": {\n \"artistName\": \"Morgan Wallen\",\n \"overallRank\": 0,\n \"videos\": 0,\n \"totalVideos\": 0,\n \"metricValue\": 0,\n \"metricDiff\": null,\n \"avgPosition\": 0,\n \"score\": 3075628,\n \"totalFollowers\": 0,\n \"velocity\": null,\n \"isComeback\": false,\n \"plays\": 0,\n \"playsWeekly\": 0,\n \"counts\": 0,\n \"index\": 0,\n \"comments\": 0\n },\n \"targetExtras\": {\n \"airplayStreams\": 102858,\n \"amazonEdPlaylistCount\": 18,\n \"amazonPlaylistCount\": 19,\n \"anghamiLikes\": 197,\n \"anghamiPlays\": 7010,\n \"deezerEdPlaylistCount\": 1,\n \"deezerEdPlaylistTotalReach\": 97790,\n \"deezerPlaylistCount\": 5,\n \"deezerPlaylistTotalReach\": 105309,\n \"itunesEdPlaylistCount\": 24,\n \"itunesPlaylistCount\": 59,\n \"melonComments\": 1,\n \"melonLikes\": 144,\n \"pandoraLifetimeStationsAdded\": 20796,\n \"pandoraLifetimeStreams\": 79701790,\n \"shazamCount\": 721541,\n \"siriusxmStreams\": 54,\n \"soundcloudPlays\": 2189646,\n \"spotifyEdPlaylistCount\": 11,\n \"spotifyEdPlaylistTotalReach\": 3568423,\n \"spotifyPlaylistCount\": 21332,\n \"spotifyPlaylistTotalReach\": 21575229,\n \"spotifyPlays\": 180354830,\n \"spotifyPopularity\": 81,\n \"tiktokTopVideosComments\": 78672,\n \"tiktokTopVideosLikes\": 4310675,\n \"tiktokTopVideosViews\": 44090062,\n \"youtubeComments\": 5577,\n \"youtubeEdPlaylistCount\": 0,\n \"youtubeEdPlaylistTotalReach\": 0,\n \"youtubeLikes\": 452287,\n \"youtubePlaylistCount\": 36,\n \"youtubePlaylistTotalReach\": 2663604436,\n \"youtubeViews\": 40973056\n },\n \"isChartGenre\": false\n },\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/pandora/track/top_spins?latest=true&date=2025-06-03", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/qq/", - "title": "QQ Music", - "name": "GetQQChart", - "version": "1.0.0", - "description": "

Get data insights for the top tracks on QQ Music on a given day

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Charting Date

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Number of entries.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

List of results.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.id", - "description": "

Chartmetric track id.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.image_url", - "description": "

image url.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.isrc", - "description": "

Track isrc

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.description", - "description": "

Description

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.created_at", - "description": "

Date created

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.modified_at", - "description": "

Date modified

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.tags", - "description": "

genres

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.cm_track", - "description": "

Chartmetric track id

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.cm_artist", - "description": "

Chartmetric artist id

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_names", - "description": "

Artist names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.code2s", - "description": "

Country codes

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_images", - "description": "

Artist images

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_covers", - "description": "

Artist covers

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.spotify_track_ids", - "description": "

Spotify track ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.spotify_album_ids", - "description": "

Spotify album ids

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.spotify_duration_ms", - "description": "

Spotify duration

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_track_ids", - "description": "

iTunes track ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_album_ids", - "description": "

iTunes album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.storefronts", - "description": "

Storefronts

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.deezer_track_ids", - "description": "

Deezer track ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.deezer_album_ids", - "description": "

Deezer album ids

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.deezer_duration", - "description": "

Deezer duration

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.amazon_track_ids", - "description": "

Amazon track ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.amazon_album_ids", - "description": "

Amazon album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_ids", - "description": "

Chartmetric album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_names", - "description": "

Album names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_upc", - "description": "

Album upc

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_label", - "description": "

Album labels

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.release_dates", - "description": "

Release dates

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.added_at", - "description": "

Added date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.qq", - "description": "" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.velocity", - "description": "

Velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.pre_rank", - "description": "

Previous rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.peak_rank", - "description": "

Top rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.peak_date", - "description": "

Peak date of top rank

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.time_on_chart", - "description": "

Track time on chart

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.rankStats", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rankStats.rank", - "description": "

Rank of the track

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.rankStats.timestp", - "description": "

Track time stamp

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": {\n \"length\": 47,\n \"data\": [\n {\n \"id\": 32150075,\n \"image_url\": \"https://is2-ssl.mzstatic.com/image/thumb/Music114/v4/b8/27/bd/b827bda8-c9ed-7c74-e09a-218231716cd7/20UMGIM83351.rgb.jpg/160x160bb.jpeg\",\n \"isrc\": \"USUM72018806\",\n \"name\": \"Teach Me How To Love\",\n \"description\": null,\n \"created_at\": \"2020-12-03T14:04:14.288Z\",\n \"modified_at\": \"2020-12-03T14:04:14.288Z\",\n \"tags\": \"Pop,Music\",\n \"cm_track\": 32150075,\n \"cm_artist\": [\n 4934\n ],\n \"artist_names\": [\n \"Shawn Mendes\"\n ],\n \"code2s\": [\n \"CA\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/809b93c47fca237138e53968d86682f09d0f45a2\"\n ],\n \"artist_covers\": [\n null\n ],\n \"spotify_track_ids\": [\n \"7p5vt5dzzshlu0x78C6jE8\",\n \"0tuhCqrt3RPwCJC3aGYCFo\",\n \"5LZtB6nxvjIhUoElp3Zqk0\"\n ],\n \"spotify_album_ids\": [\n \"2sDPTkswUWDLtZYiOzubTe\",\n \"01haNCctw4EUPl8xUHVhdl\",\n \"3Lp4JKk2ZgNkybMRS3eZR5\"\n ],\n \"spotify_duration_ms\": 202356,\n \"itunes_track_ids\": [\n \"1543627484\",\n \"1543508237\",\n \"1533345328\"\n ],\n \"itunes_album_ids\": [\n \"1543627479\",\n \"1543508230\",\n \"1533345318\"\n ],\n \"storefronts\": [\n \"US\",\n \"AR\",\n \"AU\"\n ],\n \"deezer_track_ids\": [\n \"1165396812\"\n ],\n \"deezer_album_ids\": [\n 190869502\n ],\n \"deezer_duration\": 202,\n \"amazon_track_ids\": [\n \"B08KFPMLN7\"\n ],\n \"amazon_album_ids\": [\n \"B08KFN4C5B\"\n ],\n \"album_ids\": [\n 7100374,\n 7100925,\n 7113940,\n 7119128\n ],\n \"album_names\": [\n \"Wonder\",\n \"Wonder\",\n \"Wonder (Holiday Deluxe)\",\n \"Wonder (Deluxe)\"\n ],\n \"album_upc\": [\n \"602435247595\",\n \"602435247588\",\n \"602435623795\",\n \"602435633398\"\n ],\n \"album_label\": [\n \"Shawn Mendes LP4-5 PS/ Island\",\n \"Shawn Mendes LP4-5 PS/ Island\",\n \"Shawn Mendes LP4-5 PS/ Island\",\n \"Shawn Mendes LP4-5 PS/ Island\"\n ],\n \"release_dates\": [\n \"2020-12-04T00:00:00.000Z\",\n \"2020-12-04T00:00:00.000Z\",\n \"2020-12-05T00:00:00.000Z\",\n \"2020-12-07T00:00:00.000Z\"\n ],\n \"rank\": 1,\n \"added_at\": \"2020-12-10T00:00:00.000Z\",\n \"qq\": \"001tyoix1qWeqe\",\n \"velocity\": 28.4285714285714,\n \"pre_rank\": null,\n \"peak_rank\": 1,\n \"peak_date\": \"2020-12-10T00:00:00.000Z\",\n \"time_on_chart\": \"1\",\n \"rankStats\": [\n {\n \"rank\": 1,\n \"timestp\": \"2020-12-10T00:00:00.000Z\"\n }\n ]\n }, \n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/qq?date=2019-4-11", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/shazam", - "title": "Shazam", - "name": "GetShazamChart", - "version": "1.0.0", - "description": "

Gather data insights on tracks on Shazam Charts, including latest position, time on chart and position changes over time.

Given the appropriate filters, get data specific to charting country, city, and chart genre.

This endpoint is extremely useful for identifying location-specific music trends, filtering by not only country but also city.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "GLOBAL", - "US", - "AR", - "AT", - "AU", - "BE", - "BG", - "BR", - "BY", - "CA", - "CH", - "CL", - "CN", - "CO", - "CR", - "CZ", - "DE", - "DK", - "EC", - "EG", - "ES", - "FI", - "FR", - "GB", - "GR", - "HR", - "HU", - "ID", - "IE", - "IL", - "IN", - "IR", - "IT", - "JP", - "KR", - "KZ", - "MA", - "MX", - "MY", - "NL", - "NO", - "NZ", - "PE", - "PL", - "PT", - "RO", - "RU", - "SA", - "SE", - "SG", - "TH", - "TR", - "TW", - "UA", - "UY", - "VE", - "ZA" - ], - "optional": false, - "field": "country_code", - "description": "

Country Code

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Charting date

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "San Francisco", - "..." - ], - "optional": true, - "field": "city", - "description": "

Chart City. City parameter may not return any results if country_code is GLOBAL. You can find valid cities for a country in Charts - Shazam (Cities) endpoint.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "offset", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "alternative", - "rock", - "french-pop", - "film-tv-and-stage", - "randb-soul", - "latin", - "reggae-dancehall", - "k-pop", - "hip-hop-rap", - "worldwide", - "singer-songwriter", - "dance", - "afrobeats", - "house", - "electronic", - "pop", - "country", - "regional-mexicano" - ], - "optional": true, - "field": "genre", - "description": "

Genre name. If not set, returns charts with all genres.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Number of entries.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

List of results

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.id", - "description": "

Shazam chart id

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rank", - "description": "

Track Rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.added_at", - "description": "

Track Added date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.code2", - "description": "

Track country code

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.city", - "description": "

Track city

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.shazam_track_id", - "description": "

Shazam Track Id. Note: this field was deprecated by Shazam and is no longer available after January 2024.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.itunes_id", - "description": "

Track iTunes Id

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.velocity", - "description": "

Track velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.pre_rank", - "description": "

Track Pre rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.peak_rank", - "description": "

Track peak rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.peak_date", - "description": "

Track peak date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.time_on_chart", - "description": "

Track time on chart

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

Shazam chart name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.isrc", - "description": "

Track ISRC Code

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.image_url", - "description": "

Shazam image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.itunes_track_id", - "description": "

iTunes Track id

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.cm_track", - "description": "

Chartmetric Track id

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.cm_artist", - "description": "

Chartmetric Artist Ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_names", - "description": "

Chartmetric Artist Names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.code2s", - "description": "

Artist country codes

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_images", - "description": "

Artist image urls

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_covers", - "description": "

Artist covers

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_artist_ids", - "description": "

iTunes Artist Ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_artist_names", - "description": "

iTunes Artist Names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_track_ids", - "description": "

iTunes Track Ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_album_ids", - "description": "

iTunes Album Ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.storefronts", - "description": "

Storefronts

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_ids", - "description": "

Album Ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_names", - "description": "

Album Names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_upc", - "description": "

Album UPC's

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_label", - "description": "

Album labels

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_album_id", - "description": "

iTunes Album Ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.release_dates", - "description": "

Album release dates

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.rankStats", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rankStats.rank", - "description": "

Rank of the track

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.rankStats.timestp", - "description": "

Timestamp of rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.num_of_shazams", - "description": "

Number of Shazams

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": {\n \"length\": 200,\n \"data\": [\n {\n \"id\": \"267429991\",\n \"name\": \"Makeba\",\n \"isrc\": \"FR9W11508712\",\n \"image_url\": \"https://is5-ssl.mzstatic.com/image/thumb/Music124/v4/71/64/ff/7164ff89-2654-ecd8-7264-cd4a71f6da44/mzm.krfzmdef.jpg/400x400cc.jpg\",\n \"itunes_track_id\": \"1046165672\",\n \"cm_track\": 12252993,\n \"cm_artist\": [\n 4137\n ],\n \"artist_names\": [\n \"Jain\"\n ],\n \"code2s\": [\n \"FR\"\n ],\n \"artist_images\": [\n null\n ],\n \"artist_covers\": [\n null\n ],\n \"itunes_artist_ids\": [\n 334329603\n ],\n \"itunes_artist_names\": [\n \"Jain\"\n ],\n \"itunes_track_ids\": [\n \"1046165672\"\n ],\n \"itunes_album_ids\": [\n \"1046165664\"\n ],\n \"storefronts\": [\n \"US\"\n ],\n \"album_ids\": [\n 13132390\n ],\n \"album_names\": [\n \"Zanaka\"\n ],\n \"album_upc\": [\n \"886445529944\"\n ],\n \"album_label\": [\n \"℗ 2015 Spookland\"\n ],\n \"itunes_album_id\": [\n \"1046165664\"\n ],\n \"release_dates\": [\n \"2015-11-06T00:00:00.000Z\"\n ],\n \"composer_name\": \"Jain\",\n \"album\": [\n {\n \"id\": 566583,\n \"label\": \"Columbia\",\n \"name\": \"Hope - EP\",\n \"release_date\": \"2015-06-22\",\n \"upc\": \"886445263084\"\n }\n ],\n \"duration_ms\": 249533,\n \"explicit\": false,\n \"score\": 0.024415039877,\n \"songwriter\": [\n \"Jain\"\n ],\n \"spotify_popularity\": 85,\n \"artists\": [\n {\n \"code2\": \"FR\",\n \"id\": 4137,\n \"name\": \"Jain\"\n }\n ],\n \"rank\": 1,\n \"added_at\": \"2023-06-27T00:00:00.000Z\",\n \"code2\": \"US \",\n \"shazam_track_id\": \"267429991\",\n \"itunes_id\": 131062,\n \"velocity\": 0,\n \"pre_rank\": 1,\n \"peak_rank\": 1,\n \"peak_date\": \"2023-06-27T00:00:00.000Z\",\n \"time_on_chart\": 32,\n \"rankStats\": [\n {\n \"rank\": 1,\n \"timestp\": \"2023-06-20\"\n },\n {\n \"rank\": 1,\n \"timestp\": \"2023-06-21\"\n },\n {\n \"rank\": 1,\n \"timestp\": \"2023-06-22\"\n },\n {\n \"rank\": 1,\n \"timestp\": \"2023-06-23\"\n },\n {\n \"rank\": 1,\n \"timestp\": \"2023-06-24\"\n },\n {\n \"rank\": 1,\n \"timestp\": \"2023-06-25\"\n },\n {\n \"rank\": 1,\n \"timestp\": \"2023-06-26\"\n },\n {\n \"rank\": 1,\n \"timestp\": \"2023-06-27\"\n }\n ],\n \"num_of_shazams\": 6904781\n },\n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/shazam?latest=true&country_code=US&city=San%20Francisco&genre=pop", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/shazam/:country_code/cities", - "title": "Shazam (Cities)", - "name": "GetShazamCities", - "version": "1.0.0", - "description": "

On top of country filters, our Shazam endpoint allows for city level filtering.

Get List of city for a particular country to be used in city parameters.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "GLOBAL", - "US", - "AR", - "AT", - "AU", - "BE", - "BG", - "BR", - "BY", - "CA", - "CH", - "CL", - "CN", - "CO", - "CR", - "CZ", - "DE", - "DK", - "EC", - "EG", - "ES", - "FI", - "FR", - "GB", - "GR", - "HR", - "HU", - "ID", - "IE", - "IL", - "IN", - "IR", - "IT", - "JP", - "KR", - "KZ", - "MA", - "MX", - "MY", - "NL", - "NO", - "NZ", - "PE", - "PL", - "PT", - "RO", - "RU", - "SA", - "SE", - "SG", - "TH", - "TR", - "TW", - "UA", - "UY", - "VE", - "ZA" - ], - "optional": false, - "field": "country_code", - "description": "

Country Code

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "cities", - "description": "

List of cities for the country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Length of the cities array.

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": {\n \"length\": 202,\n \"cities\": [\n \"Aberdeen SD\",\n \"Aberdeen WA\",\n \"Abilene\",\n \"Akron\",\n \"Albany\",\n \"Albuquerque\",\n \"Allentown\",\n \"Anaheim\",\n \"Anchorage\",\n \"Asheville\",\n \"Atlanta\",\n \"Aurora\",\n \"Austin\",\n \"Bakersfield\",\n \"Baltimore\",\n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/shazam/US/cities", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/soundcloud", - "title": "SoundCloud (Legacy)", - "name": "GetSoundCloudChartLegacy", - "version": "1.0.0", - "description": "

Gather data insights on tracks on SoundCloud Charts, including latest position, time on chart and position changes over time.

Given the appropriate filters, get data specific to charting country, kind, and genre.

This endpoint is extremely useful for identifying upcoming and indie music that is usually not charting on other streaming services.

This data is updated weekly only (on Fridays).

Note: This endpoint has been deprecated by SoundCloud and replaced with the new Charts - SoundCloud endpoint. We are providing this legacy endpoint only for archival purposes.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "IE", - "AU", - "NL", - "CA", - "GLOBAL", - "US", - "FR", - "NZ", - "GB", - "DE" - ], - "optional": false, - "field": "country_code", - "description": "

Country Code.

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Charting Date

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "top", - "trending" - ], - "optional": false, - "field": "kind", - "description": "" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "all-music", - "pop", - "hiphoprap", - "country", - "rock", - "jazzblues", - "classical", - "reggae", - "house", - "folksingersongwriter", - "techno", - "trance", - "disco", - "rbsoul", - "world", - "latin", - "piano", - "reggaeton", - "alternativerock", - "indie", - "triphop", - "electronic", - "deephouse", - "drumbass", - "dancehall", - "soundtrack", - "ambient", - "dubstep", - "danceedm", - "trap", - "metal" - ], - "optional": false, - "field": "genre", - "description": "" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "allowedValues": [ - "true/false" - ], - "optional": true, - "field": "raw", - "description": "

Returns response excluding peak_rank and peak_date. raw is true by default and has faster response time.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Number of entries.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

List of results.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.id", - "description": "

Soundcloud chart id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

Track name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.isrc", - "description": "

Track isrc.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.image_url", - "description": "

image url.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.cm_track", - "description": "

Chartmetric track id.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_names", - "description": "

Artist names.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_names", - "description": "

Album names.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rank", - "description": "

Current rank.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.added_at", - "description": "

Added date.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.country", - "description": "

Country code.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.genre", - "description": "

genre.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.kind", - "description": "

Charts type.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_track_id", - "description": "

Soundcloud track id.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.playcount_all", - "description": "

Total playcount.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.playcount_weekly", - "description": "

Weekly playcount.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.artwork_url", - "description": "

Artwork url.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.permalink_url", - "description": "

Permalink url.

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "data.public", - "description": "

public.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.publisher", - "description": "

publisher

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.writer_composer", - "description": "

Composer.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.created_at", - "description": "

Created date.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.soundcloud_user", - "description": "

Soundcloud user data.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_user.avator_url", - "description": "

Url of Soundcloud user avator

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_user.city", - "description": "

City of user

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.soundcloud_user.comments_count", - "description": "

Count of comments made by user

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_user.country_code", - "description": "

Country

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_user.create_at", - "description": "

Created date

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.soundcloud_user.creator_subscriptions", - "description": "

Subscriptions info

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "data.soundcloud_user.creator_subscriptions.product", - "description": "

Product Subscribed product info

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_user.description", - "description": "

User description

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.soundcloud_user.followers_count", - "description": "

Count of followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.soundcloud_user.followings_count", - "description": "

Count of followings

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_user.first_name", - "description": "

First name of user

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_user.full_name", - "description": "

Full name of user

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.soundcloud_user.groups_count", - "description": "

Count of groups

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.soundcloud_user.id", - "description": "

User id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_user.kind", - "description": "

Account type

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_user.last_modified", - "description": "

last modified date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_user.last_name", - "description": "

Last name of user

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.soundcloud_user.likes_count", - "description": "

Count of likes

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.soundcloud_user.playlist_likes_count", - "description": "

Count of likes on playlist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_user.permalink", - "description": "

Permalink

" - }, - { - "group": "Success 200", - "type": "SString", - "optional": false, - "field": "data.soundcloud_user.permalink_url", - "description": "

Permalink url

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.soundcloud_user.playlist_count", - "description": "

Count of playlist

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.soundcloud_user.reposts_count", - "description": "

Count of reposts

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.soundcloud_user.track_count", - "description": "

Count of tracks

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_user.uri", - "description": "

Uri link

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_user.urn", - "description": "

Urn link

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_user.username", - "description": "

Username

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "data.soundcloud_user.verified", - "description": "

Verified status

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.soundcloud_user.visuals", - "description": "" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "data.soundcloud_user.badges", - "description": "" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "data.soundcloud_user.badges.pro_unlimited", - "description": "

Pro subscription status

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "data.soundcloud_user.badges.verified", - "description": "

Verified status

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.artist", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.album_title", - "description": "

Album title

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.modified_at", - "description": "

modified date.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.velocity", - "description": "

Velocity.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.pre_rank", - "description": "

Previous rank.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.time_on_chart", - "description": "

Track time on chart.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.rankStats", - "description": "

Track rank history.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rankStats.rank", - "description": "

Rank of the track.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.rankStats.timestp", - "description": "

Timestamp of rank.

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": {\n \"length\": 200,\n \"data\": [\n {\n \"id\": \"879400330\",\n \"name\": \"Whoopty\",\n \"isrc\": \"USA2P2031043\",\n \"image_url\": \"https://i1.sndcdn.com/artworks-lebqU1UanVuv-0-large.jpg\",\n \"cm_track\": 31140457,\n \"artist_names\": [\n \"CJ\"\n ],\n \"album_names\": [\n null\n ],\n \"rank\": 1,\n \"added_at\": \"2020-12-19T00:00:00.000Z\",\n \"country\": \"US \",\n \"genre\": \"Hip-Hop\",\n \"kind\": \"top \",\n \"soundcloud_track_id\": \"879400330\",\n \"playcount_all\": \"15911600\",\n \"playcount_weekly\": \"795351\",\n \"artwork_url\": \"https://i1.sndcdn.com/artworks-lebqU1UanVuv-0-large.jpg\",\n \"permalink_url\": \"https://soundcloud.com/cj50058/whoopty-1\",\n \"public\": true,\n \"publisher\": null,\n \"writer_composer\": null,\n \"created_at\": \"2020-08-21T02:22:11.000Z\",\n \"soundcloud_user\": {\n \"avatar_url\": \"https://a1.sndcdn.com/images/default_avatar_large.png?1602832950\",\n \"city\": null,\n \"comments_count\": 0,\n \"country_code\": null,\n \"created_at\": \"2016-06-24T14:51:37Z\",\n \"creator_subscriptions\": [\n {\n \"product\": {\n \"id\": \"creator-pro-unlimited\"\n }\n }\n ],\n \"creator_subscription\": {\n \"product\": {\n \"id\": \"creator-pro-unlimited\"\n }\n },\n \"description\": null,\n \"followers_count\": 1353,\n \"followings_count\": 0,\n \"first_name\": \"\",\n \"full_name\": \"\",\n \"groups_count\": 0,\n \"id\": 235808580,\n \"kind\": \"user\",\n \"last_modified\": \"2020-10-06T17:43:27Z\",\n \"last_name\": \"\",\n \"likes_count\": 0,\n \"playlist_likes_count\": 0,\n \"permalink\": \"cj50058\",\n \"permalink_url\": \"https://soundcloud.com/cj50058\",\n \"playlist_count\": 4,\n \"reposts_count\": null,\n \"track_count\": 53,\n \"uri\": \"https://api.soundcloud.com/users/235808580\",\n \"urn\": \"soundcloud:users:235808580\",\n \"username\": \"CJ\",\n \"verified\": false,\n \"visuals\": null,\n \"badges\": {\n \"pro_unlimited\": true,\n \"verified\": false\n }\n },\n \"artist\": \"CJ\",\n \"album_title\": null,\n \"modified_at\": \"2020-10-20T09:09:07.229Z\",\n \"velocity\": 0.142857142857143,\n \"pre_rank\": 1,\n \"time_on_chart\": \"98\",\n \"rankStats\": [\n {\n \"rank\": 2,\n \"timestp\": \"2020-12-12T00:00:00.000Z\"\n },\n {\n \"rank\": 2,\n \"timestp\": \"2020-12-13T00:00:00.000Z\"\n }, ...\n ]\n }, ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/soundcloud?date=2019-02-01&country_code=US&kind=trending&genre=all-music&offset=0&raw=true", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/soundcloud/track/:chartType", - "title": "SoundCloud", - "name": "GetSoundCloudTracksChart", - "version": "1.0.0", - "description": "

Returns SoundCloud's new Tracks Chart data, including historical chart positions and streaming trends over time.

Use this endpoint to analyze emerging music trends and identify top-performing tracks on SoundCloud.

Note: This data is updated version of the Charts - SoundCloud (Legacy) endpoint. SoundCloud has deprecated its legacy charts and now provides chart data only for the US and UK regions.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "top" - ], - "optional": false, - "field": "chartType", - "description": "

Chart type.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "US", - "UK" - ], - "optional": false, - "field": "code2", - "description": "

Chart country code.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Chart genre.

Use the /api/charts/genres/:platform endpoint to retrieve available genres for a platform and country.

US example: /api/charts/genres/soundcloud?code2=US
=> all music genres, artist pro, country, electronic, folk, hip hop, latin, new & hot, next pro, pop, r&b, rock

UK example: /api/charts/genres/soundcloud?code2=UK
=> all music genres, artist pro, dance, folk, hip hop, indie, new & hot, next pro, pop, r&b, rock

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "description": "

Chart date.

Use the /api/charts/:streamingType/dates endpoint to retrieve available dates for a given country and genre.

Example: /api/charts/soundcloud/dates?fromDaysAgo=9999&country=US&genre=all music genres

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "defaultValue": "false", - "description": "

If true, returns the most recent data regardless of date.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

Number of entries to return.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Number of entries to skip before returning results.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "entityId", - "description": "

Internal entity identifier.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "targetId", - "description": "

SoundCloud track identifier.

" - }, - { - "group": "Success 200", - "type": "String", - "allowedValues": [ - "US", - "UK" - ], - "optional": false, - "field": "country", - "description": "

Chart region.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cityId", - "description": "

City identifier (0 if not applicable).

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Chart genre.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "rank", - "description": "

Current chart position.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "rankDiff", - "description": "

Rank change compared to the previous period.

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "rankTrend", - "description": "

Historical rank trend.

" - }, - { - "group": "Success 200", - "type": "String", - "size": "YYYY-MM-DD", - "optional": false, - "field": "rankTrend.timestp", - "description": "

Date of the rank record.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "rankTrend.rank", - "description": "

Rank value on that date.

" - }, - { - "group": "Success 200", - "type": "String", - "size": "YYYY-MM-DD", - "optional": false, - "field": "peakDate", - "description": "

Date when the track reached its highest rank.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "peakRank", - "description": "

Best chart position achieved.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "velocity7Day", - "description": "

Seven-day velocity metric.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "periodsOnChart", - "description": "

Total number of chart appearances.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "consecutivePeriods", - "description": "

Consecutive chart appearances.

" - }, - { - "group": "Success 200", - "type": "String", - "size": "YYYY-MM-DD", - "optional": false, - "field": "chartedDate", - "description": "

Chart date corresponding to this record.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "imageUrl", - "description": "

Cover image URL.

" - }, - { - "group": "Success 200", - "type": "String", - "size": "YYYY-MM-DD HH:mm:ss", - "optional": false, - "field": "createdAt", - "description": "

Record creation timestamp.

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "artists", - "description": "

List of artists.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "artists.id", - "description": "

Artist identifier.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": true, - "field": "artists.code2", - "description": "

Artist country code.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": true, - "field": "artists.careerStage", - "description": "

Artist career stage.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": true, - "field": "artists.imageUrl", - "description": "

Artist image URL.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "album", - "description": "

Album information.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "album.id", - "description": "

Album identifier.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.name", - "description": "

Album name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.label", - "description": "

Record label.

" - }, - { - "group": "Success 200", - "type": "String", - "size": "YYYY-MM-DDTHH:mm:ss.SSSZ", - "optional": false, - "field": "album.releaseDate", - "description": "

Album release date.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isrc", - "description": "

ISRC code.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "externalId", - "description": "

External platform identifier.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "optionalFields", - "description": "

Additional metadata.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "optionalFields.artistName", - "description": "

Primary artist name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "optionalFields.name", - "description": "

Track name (redundant field).

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "optionalFields.imageUrl", - "description": "

Alternate image URL.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": true, - "field": "optionalFields.albumName", - "description": "

Album name.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "optionalFields.overallRank", - "description": "

Overall chart rank.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "optionalFields.metricValue", - "description": "

Metric value.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "optionalFields.score", - "description": "

Internal score.

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": true, - "field": "optionalFields.isComeback", - "description": "

Comeback indicator.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "optionalFields.plays", - "description": "

Play count.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "optionalFields.totalFollowers", - "description": "

Total followers.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "targetExtras", - "description": "

Platform-level metrics.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.soundcloudPlays", - "description": "

SoundCloud play count.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.spotifyPlays", - "description": "

Spotify play count.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.spotifyPopularity", - "description": "

Spotify popularity score.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.spotifyPlaylistCount", - "description": "

Spotify playlist count.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.spotifyPlaylistTotalReach", - "description": "

Spotify playlist reach.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.spotifyEdPlaylistCount", - "description": "

Spotify editorial playlist count.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.spotifyEdPlaylistTotalReach", - "description": "

Spotify editorial playlist reach.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.amazonPlaylistCount", - "description": "

Amazon Music playlist count.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.amazonEdPlaylistCount", - "description": "

Amazon editorial playlist count.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.anghamiPlays", - "description": "

Anghami play count.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.pandoraLifetimeStreams", - "description": "

Pandora lifetime streams.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.youtubeViews", - "description": "

YouTube view count.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.youtubeLikes", - "description": "

YouTube like count.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.youtubeComments", - "description": "

YouTube comment count.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.tiktokTopVideosViews", - "description": "

TikTok top video views.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.tiktokTopVideosLikes", - "description": "

TikTok top video likes.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": true, - "field": "targetExtras.tiktokTopVideosComments", - "description": "

TikTok top video comments.

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "isChartGenre", - "description": "

Indicates if the chart is genre-based.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "entityGenre", - "description": "

Genres associated with the track.

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": [\n {\n \"entityId\": 153482095,\n \"targetId\": \"2176782261\",\n \"country\": \"US\",\n \"cityId\": 0,\n \"genre\": \"all music genres\",\n \"rank\": 1,\n \"rankDiff\": 2,\n \"rankTrend\": [\n {\n \"timestp\": \"2025-11-24\",\n \"rank\": \"3\"\n },\n {\n \"timestp\": \"2025-11-25\",\n \"rank\": \"3\"\n },\n {\n \"timestp\": \"2025-11-26\",\n \"rank\": \"3\"\n },\n {\n \"timestp\": \"2025-11-27\",\n \"rank\": \"1\"\n }\n ],\n \"peakDate\": \"2025-10-30\",\n \"peakRank\": 1,\n \"velocity7Day\": 0.2857143,\n \"periodsOnChart\": 57,\n \"consecutivePeriods\": 57,\n \"chartedDate\": \"2025-11-27\",\n \"name\": \"What You Is\",\n \"imageUrl\": \"https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/49/28/fc/4928fc53-b49f-5e65-45e9-404eca11726f/25UM1IM51211.rgb.jpg/160x160bb.jpg\",\n \"createdAt\": \"2025-11-28 07:23:15\",\n \"artists\": [\n {\n \"careerStage\": \"superstar\",\n \"code2\": \"US\",\n \"id\": 215373,\n \"name\": \"YoungBoy Never Broke Again\",\n \"imageUrl\": \"https://i.scdn.co/image/ab6761610000e5eb776e469c846c205685141317\"\n },\n {\n \"careerStage\": \"mainstream\",\n \"code2\": \"US\",\n \"id\": 4504452,\n \"name\": \"Mellow Rackz\",\n \"imageUrl\": \"https://i.scdn.co/image/ab6761610000e5eb8b0582340275b3b3dc47786f\"\n }\n ],\n \"album\": {\n \"id\": 47470879,\n \"name\": \"What You Is - Single\",\n \"label\": \"Never Broke Again, LLC/Motown Records\",\n \"releaseDate\": \"2025-09-26T00:00:00.000Z\"\n },\n \"isrc\": \"USUG12508898\",\n \"externalId\": \"2176782261\",\n \"optionalFields\": {\n \"artistName\": \"YoungBoy Never Broke Again\",\n \"overallRank\": 0,\n \"videos\": 0,\n \"totalVideos\": 0,\n \"metricValue\": 0,\n \"metricDiff\": null,\n \"avgPosition\": 0,\n \"score\": 0,\n \"totalFollowers\": 0,\n \"velocity\": null,\n \"isComeback\": false,\n \"plays\": 0,\n \"playsWeekly\": 0,\n \"counts\": 0,\n \"index\": 0,\n \"comments\": 0,\n \"id\": \"2176782261\",\n \"platformId\": \"2176782261\",\n \"name\": \"What You Is\",\n \"imageUrl\": \"https://i1.sndcdn.com/artworks-GOzyCU2UJ0j8-0-large.jpg\",\n \"albumName\": \"What You Is\"\n },\n \"targetExtras\": {\n \"amazonEdPlaylistCount\": 16,\n \"amazonPlaylistCount\": 17,\n \"anghamiLikes\": 83,\n \"anghamiPlays\": 2276,\n \"deezerEdPlaylistCount\": 1,\n \"deezerEdPlaylistTotalReach\": 1086999,\n \"deezerPlaylistCount\": 3,\n \"deezerPlaylistTotalReach\": 1093462,\n \"itunesEdPlaylistCount\": 12,\n \"itunesPlaylistCount\": 23,\n \"pandoraLifetimeStationsAdded\": 4922,\n \"pandoraLifetimeStreams\": 1800475,\n \"shazamCount\": 534190,\n \"soundcloudPlays\": 2455776,\n \"spotifyEdPlaylistCount\": 3,\n \"spotifyEdPlaylistTotalReach\": 6760616,\n \"spotifyPlaylistCount\": 2620,\n \"spotifyPlaylistTotalReach\": 9623313,\n \"spotifyPlays\": 16441851,\n \"spotifyPopularity\": 77,\n \"tiktokTopVideosComments\": 34,\n \"tiktokTopVideosLikes\": 452,\n \"tiktokTopVideosViews\": 45618,\n \"youtubeComments\": 3741,\n \"youtubeEdPlaylistCount\": 3,\n \"youtubeEdPlaylistTotalReach\": 0,\n \"youtubeLikes\": 177638,\n \"youtubePlaylistCount\": 4,\n \"youtubePlaylistTotalReach\": 34512392,\n \"youtubeViews\": 11707771\n },\n \"isChartGenre\": true,\n \"entityGenre\": \"trap\"\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/soundcloud/track/top?code2=US&genre=all music genres&date=2025-11-27&latest=false&limit=50&offset=0", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/spotify/artists", - "title": "Spotify (Artists)", - "name": "GetSpotifyArtistsChart", - "version": "1.0.0", - "description": "

Get data insights for artists on Spotify Charts, including historical chart positions trend and listener counts.
Given the appropriate filters, get data specific to interval and chart type (e.g. Monthly Popularity).
This endpoint is useful for identifying trending artists on various Spotify Charts.
This data will be cached daily.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Charting Date '2018-12-31'

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "monthly_listeners", - "popularity", - "followers", - "playlist_count", - "playlist_reach" - ], - "optional": false, - "field": "type", - "description": "

Chart Type

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "daily", - "weekly", - "monthly" - ], - "optional": false, - "field": "interval", - "description": "

Chart Interval

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Artist name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Image url.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isni", - "description": "

Artist isni.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "code2", - "description": "

Country code.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "hometown_city", - "description": "

Hometown city.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "current_city", - "description": "

Current city.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_followers", - "description": "

Spotify followers.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_popularity", - "description": "

Spotify popularity.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_monthly_listeners", - "description": "

Spotify monthly listeners.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "deezer_fans", - "description": "

Deezer fans.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "tags", - "description": "

List of genre tags.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_artist_ids", - "description": "

Spotify artist ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "itunes_artist_ids", - "description": "

iTunes artist ids.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "deezer_artist_ids", - "description": "

Deezer artist ids.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_artist_rank", - "description": "

Chartmetric artist rank.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "cm_artist_score", - "description": "

Chartmetric artist score.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "amazon_artist_ids", - "description": "

Amazon artist ids.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "timestp", - "description": "

Time stamp of the rank.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "monthly_playlist_reach", - "description": "

Change in the sum of playlist followers for all playlists including the artist's tracks.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

Rank on the chart.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "change", - "description": "

Change in rank over interval.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "streak", - "description": "

Intervals artist has charted in a row.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "peak_date", - "description": "

Date of top rank.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "peak_rank", - "description": "

Peak rank on the chart.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "time_on_chart", - "description": "

Days artist has been on chart.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "rankStats", - "description": "

Artist rank history.

" - }, - { - "group": "Success 200", - "type": "Date[Object]", - "optional": false, - "field": "rankStats.date", - "description": "

Date rank was recorded.

" - }, - { - "group": "Success 200", - "type": "Integer[Object]", - "optional": false, - "field": "rankStats.rank", - "description": "

Rank at date.

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": " {\n \"obj\": {\n \"length\": 200,\n \"data\": [\n {\n \"id\": 4236,\n \"name\": \"Becky G\",\n \"image_url\": \"https://i.scdn.co/image/ab67616100005174653ae0ed6e7bef49386b2f93\",\n \"isni\": \"0000000399620631\",\n \"code2\": \"us\",\n \"hometown_city\": \"Los Angeles\",\n \"current_city\": \"Los Angeles\",\n \"sp_followers\": 11040663,\n \"sp_popularity\": 85,\n \"sp_monthly_listeners\": 31895693,\n \"deezer_fans\": 41,\n \"tags\": [\n \"latin pop\",\n \"pop\",\n \"reggaeton\",\n \"latin\",\n \"dance pop\",\n \"trap latino\",\n \"rap latina\",\n \"latin viral pop\",\n \"latino\"\n ],\n \"spotify_artist_ids\": [\n \"4obzFoKoKRHIphyHzJ35G3\"\n ],\n \"itunes_artist_ids\": [\n 550411604\n ],\n \"deezer_artist_ids\": [\n \"12001184\",\n \"12338644\",\n \"4852364\",\n \"4698748\",\n \"10744180\",\n \"4419383\"\n ],\n \"cm_artist_rank\": 55,\n \"cm_artist_score\": 210419,\n \"amazon_artist_ids\": [\n \"B0088V0L5W\",\n \"B008H6E5Y6\",\n \"B008LAR6KS\",\n \"B0088XW2U2\",\n \"B0088UQDDW\",\n \"B0088YA74Y\"\n ],\n \"cm_artist\": 4236,\n \"timestp\": \"2022-03-01\",\n \"monthly_playlist_reach\": 138466271,\n \"rank\": 1,\n \"change\": null,\n \"streak\": 1,\n \"peak_date\": \"2022-03-01\",\n \"peak_rank\": 1,\n \"time_on_chart\": 10,\n \"rankStats\": [\n {\n \"date\": \"2021-03-01\",\n \"rank\": 94\n },\n {\n \"date\": \"2021-05-01\",\n \"rank\": 63\n },\n {\n \"date\": \"2021-07-01\",\n \"rank\": 152\n },\n {\n \"date\": \"2021-08-01\",\n \"rank\": 320\n },\n {\n \"date\": \"2022-03-01\",\n \"rank\": 1\n }\n ]\n }, ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/spotify/artists?date=2022-03-01&interval=monthly&type=playlist_reach", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/spotify/freshfind", - "title": "Spotify (Freshfind)", - "name": "GetSpotifyFreshfindChart", - "version": "1.0.0", - "description": "

For a given date, get tracks from the Spotify Fresh Finds Charts.

This endpoint is useful for identifying new music from independent artists and labels.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Charting date.

" - }, - { - "group": "Parameter", - "type": "Integer", - "size": "1-200", - "optional": true, - "field": "limit", - "defaultValue": "200", - "description": "

Maximum number of records to return.

" - }, - { - "group": "Parameter", - "type": "Integer", - "size": "0-10000", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Number of records to skip before starting to return results.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If true, returns the latest available data point regardless of the given date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isrc", - "description": "

Track ISRC code.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "spotify_album_id", - "description": "

Spotify album ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Track or album cover image URL.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "spotify_popularity", - "description": "

Spotify popularity score.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_track", - "description": "

Chartmetric track ID.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_artist_names", - "description": "

Spotify artist names.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track_genre", - "description": "

Track genre.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "cm_artist", - "description": "

Chartmetric artist IDs.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist_names", - "description": "

Artist names.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "code2s", - "description": "

Country codes.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist_images", - "description": "

Artist images.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist_covers", - "description": "

Artist cover images.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_artist_ids", - "description": "

Spotify artist IDs.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_track_ids", - "description": "

Spotify track IDs.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_album_ids", - "description": "

Spotify album IDs.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "spotify_duration_ms", - "description": "

Track duration in milliseconds.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_ids", - "description": "

Chartmetric album IDs.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_names", - "description": "

Album names.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_upc", - "description": "

Album UPC codes.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_label", - "description": "

Album labels.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "release_dates", - "description": "

Album release dates.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artists", - "description": "

List of artist objects associated with the track.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.career_health", - "description": "

Artist's current career trend category as shown on the dashboard.

Possible values in order: decline, gradual decline, steady, growth, explosive growth.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.career_stage", - "description": "

Artist's current career stage as shown on the dashboard.

Possible values in order: undiscovered, developing, mid-level, mainstream, superstar, legendary

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.code2", - "description": "

Artist's country code.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.id", - "description": "

Chartmetric artist ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist name.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "artists.network_strength", - "description": "

Social media network strength metrics.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.network_strength.instagram_label", - "description": "

Descriptive label for Instagram performance.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "artists.network_strength.instagram_score", - "description": "

Normalized Instagram network strength score (0–1).

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.network_strength.label", - "description": "

Overall network strength label.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "artists.network_strength.score", - "description": "

Overall normalized network strength score (0–1).

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.network_strength.youtube_label", - "description": "

Descriptive label for YouTube performance.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "artists.network_strength.youtube_score", - "description": "

Normalized YouTube network strength score (0–1).

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.image_url", - "description": "

Artist profile image URL.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "spotify_track_id", - "description": "

Spotify track ID included in the Fresh Finds playlist.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "rank", - "description": "

Rank based on the num_all_playlists. Tracks with the same count share the same rank.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "num_all_playlists", - "description": "

Total number of Spotify playlists that the track appears in.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "num_ff_playlists", - "description": "

Total number of Fresh Finds playlists that the track appears in.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "fresh_finds_playlists_ids", - "description": "

Chartmetric playlist IDs of the Fresh Finds playlists the track appears in (aligned with fresh_finds_playlists).

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "fresh_finds_playlists", - "description": "

Chartmetric playlist names of the Fresh Finds playlists the track appears in.

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": [\n {\n \"name\": \"it is what it is\",\n \"isrc\": \"NL8RL2458354\",\n \"spotify_album_id\": \"6Q5w9pBeKPyXvOJavQpju0\",\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b27329198474bc5da5a5ed0a29ff\",\n \"spotify_popularity\": 54,\n \"cm_track\": 140082586,\n \"spotify_artist_names\": [\n \"Swapa\"\n ],\n \"track_genre\": \"hip-hop/rap, rage rap\",\n \"cm_artist\": [\n 4183039\n ],\n \"artist_names\": [\n \"Swapa\"\n ],\n \"code2s\": [\n \"US\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/ab6761610000e5eb4fa205066f340450043a9dbd\"\n ],\n \"artist_covers\": [\n null\n ],\n \"spotify_artist_ids\": [\n \"37v7VwiEJ4ImKCmCrA2Z6a\"\n ],\n \"spotify_track_ids\": [\n \"1EkM7SToZWGLkJp572Y7Ab\"\n ],\n \"spotify_album_ids\": [\n \"6Q5w9pBeKPyXvOJavQpju0\"\n ],\n \"spotify_duration_ms\": 143783,\n \"album_ids\": [\n 40664378\n ],\n \"album_names\": [\n \"it is what it is\"\n ],\n \"album_upc\": [\n \"8721253329152\"\n ],\n \"album_label\": [\n \"9th St. / Hallwood\"\n ],\n \"release_dates\": [\n \"2024-11-28\"\n ],\n \"artists\": [\n {\n \"career_health\": \"steady\",\n \"career_stage\": \"mid-level\",\n \"code2\": \"US\",\n \"id\": 4183039,\n \"name\": \"Swapa\",\n \"network_strength\": {\n \"instagram_label\": \"ascendant\",\n \"instagram_score\": 0.3816599337690936,\n \"label\": \"ascendant\",\n \"score\": 0.4287280348218605,\n \"youtube_label\": \"ascendant\",\n \"youtube_score\": 0.08445393421769909\n },\n \"image_url\": \"https://i.scdn.co/image/ab6761610000e5eb4fa205066f340450043a9dbd\"\n }\n ],\n \"spotify_track_id\": \"1EkM7SToZWGLkJp572Y7Ab\",\n \"rank\": 1,\n \"num_all_playlists\": 4361,\n \"num_ff_playlists\": 1,\n \"fresh_finds_playlists_ids\": [\n 1646742\n ],\n \"fresh_finds_playlists\": [\n \"Fresh Finds Hip-Hop\"\n ]\n }, ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/spotify/freshfind?date=2025-09-25", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/spotify", - "title": "Spotify (Tracks)", - "name": "GetSpotifyTracksChart", - "version": "1.0.0", - "description": "

Get data insights for tracks on Spotify Charts, including historical chart positions trend and streaming counts over time.
Given the appropriate filters, get data specific to charting country, and chart type (Top 50 / Viral 50).
This endpoint is useful for identifying latest music trends in different countries on various Spotify Charts.
This data will be cached daily.
Spotify Weekly Viral Chart and fields current_plays, rankStats.plays for all charts are deprecated after 2022-06-02 and data after this date is not available.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Charting Date '2018-12-31'

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "GLOBAL", - "US", - "..." - ], - "optional": false, - "field": "country_code", - "description": "

Country Code (Full list in Introduction)

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "regional", - "viral" - ], - "optional": false, - "field": "type", - "description": "

Chart Type

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "daily", - "weekly" - ], - "optional": false, - "field": "interval", - "description": "

Chart Interval

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Number of data entries

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

List of reuslts

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.isrc", - "description": "

Track isrc

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.spotify_album_id", - "description": "

Spotify album id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.image_url", - "description": "

image url.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.spotify_popularity", - "description": "

Spotify popularity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.cm_track", - "description": "

Chartmetric track id

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.spotify_artist_names", - "description": "

Spotify artist names

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.track_genre", - "description": "

track genre

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.cm_artist", - "description": "

Chartmetric artist id

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_names", - "description": "

Artist names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.code2s", - "description": "

Country codes

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_images", - "description": "

Artist images

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_covers", - "description": "

Artist covers

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.spotify_artist_ids", - "description": "

Spotify artist ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.spotify_track_ids", - "description": "

Spotify track ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.spotify_album_ids", - "description": "

Spotify album ids

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.spotify_duration_ms", - "description": "

Spotify duration

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_ids", - "description": "

Chartmetric album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_names", - "description": "

Album names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_upc", - "description": "

Album upc

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_label", - "description": "

Album labels

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.release_dates", - "description": "

Release dates

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.added_at", - "description": "

Added date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.chart_type", - "description": "

Spotify chart type

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.chart_name", - "description": "

Spotify chart name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.current_plays", - "description": "

Current plays. This data does not exist after 2022-06-02.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.code2", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.spotify", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.velocity", - "description": "

Velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.pre_rank", - "description": "

Previous rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.peak_rank", - "description": "

Top rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.peak_date", - "description": "

Peak date of top rank

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.time_on_chart", - "description": "

Track time on chart

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.rankStats", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rankStats.plays", - "description": "

Number of plays. This data does not exist after 2022-06-02.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rankStats.rank", - "description": "

Rank of track

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.rankStats.timestp", - "description": "

Track time stamp

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": {\n \"length\": 200,\n \"data\": [\n {\n \"id\": 9898931,\n \"name\": \"Mo Bamba\",\n \"isrc\": \"QM24S1703585\",\n \"spotify_album_id\": \"15Id9Jrqab8IwHFirdrrLp\",\n \"image_url\": \"https://i.scdn.co/image/0e3e187e3ae399c7aa68c6c8585ec5649b481452\",\n \"spotify_popularity\": 79,\n \"cm_track\": 16994490,\n \"spotify_artist_names\": [\n \"Sheck Wes\"\n ],\n \"track_genre\": \"rap,trap,underground hip hop,vapor trap\",\n \"cm_artist\": [\n 743316\n ],\n \"artist_names\": [\n \"Sheck Wes\"\n ],\n \"code2s\": [\n \"US\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/c0f2b79210f155ffbc25a5a3900d3918566cec6b\"\n ],\n \"artist_covers\": [\n null\n ],\n \"spotify_artist_ids\": [\n \"2RDOrhPqAM4jzTRCEb19qX\"\n ],\n \"spotify_track_ids\": [\n \"1xzBco0xcoJEDXktl7Jxrr\"\n ],\n \"spotify_album_ids\": [\n \"15Id9Jrqab8IwHFirdrrLp\"\n ],\n \"spotify_duration_ms\": 183906,\n \"album_ids\": [\n 3128396\n ],\n \"album_names\": [\n \"MUDBOY\"\n ],\n \"album_upc\": [\n \"602567776024\"\n ],\n \"album_label\": [\n \"Cactus Jack Records/ G.O.O.D. Music/ Interscope Records\"\n ],\n \"release_dates\": [\n \"2018-10-05T00:00:00.000Z\"\n ],\n \"rank\": 1,\n \"added_at\": \"2018-11-01T00:00:00.000Z\",\n \"duration\": \"daily\",\n \"chart_type\": \"regional\",\n \"chart_name\": \"REGIONAL DAILY\",\n \"current_plays\": 1634932,\n \"code2\": \"US\",\n \"spotify\": 9898931,\n \"velocity\": 0.142857142857143,\n \"pre_rank\": 1,\n \"peak_rank\": 1,\n \"peak_date\": \"2018-10-29T00:00:00.000Z\",\n \"time_on_chart\": \"347\",\n \"rankStats\": [\n {\n \"plays\": 1537944,\n \"rank\": 2,\n \"timestp\": \"2018-10-25T00:00:00.000Z\"\n },\n {\n \"plays\": 1590399,\n \"rank\": 4,\n \"timestp\": \"2018-10-26T00:00:00.000Z\"\n }, ...\n ]\n }, ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/spotify?date=2018-11-01&country_code=US&interval=daily&type=regional", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/tiktok/:chart-type", - "title": "TikTok", - "name": "GetTiktokTracksChart", - "version": "1.0.0", - "description": "

Get TikTok Tracks Chart Information

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "tracks", - "videos", - "users" - ], - "optional": false, - "field": "chart-type", - "description": "

['tracks': top track chart, 'videos': trending video chart, 'users': top user chart]

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Charting Date

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "daily", - "weekly", - "all_time" - ], - "optional": true, - "field": "interval", - "defaultValue": "all_time", - "description": "

Chart Interval

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "likes", - "followers" - ], - "optional": false, - "field": "type", - "description": "

The type of user chart to retrieve. This parameter will only be used for users and will be ignored for other chart types

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

The limit of entries to be returned.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "countryChart", - "defaultValue": "false", - "description": "

If set to true, returns the country chart. Else returns of post charts

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "code2", - "description": "

Country code of the chart. Only used if countryChart is set to true;

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Number of items in the data array

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

Data object containing tracks information.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.tiktok_track_id", - "description": "

TikTok Track ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

TikTok Track Name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.isrc", - "description": "

ISRC code

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.image_url", - "description": "

Track image url

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.cm_track", - "description": "

Chartmetric's Track ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.tiktok_artist_names", - "description": "

TikTok Artist's names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.tiktok_track_ids", - "description": "

TikTok Track ID's

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_ids", - "description": "

Chartmetric Album ID's

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_names", - "description": "

Chartmetric Album Names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_upc", - "description": "

Album UPC's

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_label", - "description": "

Album Labels

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.release_dates", - "description": "

Album release dates

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rank", - "description": "

Rank on the chart

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.posts", - "description": "

Number of posts of all time, this will vary depending on what 'interval' was passed in. If 'weekly' or 'daily' was passed in, you should expect 'weekly_post' or 'daily_post'.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.added_at", - "description": "

Date this track was added

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.tiktok", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.velocity", - "description": "

Velocity of on the chart

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.pre_rank", - "description": "

Previous rank on the chart

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.peak_rank", - "description": "

Peak rank on the chart

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.peak_date", - "description": "

Date on which the peak rank was achieved

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.time_on_chart", - "description": "

Time on the chart

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.rankStats", - "description": "

Rank object containing rank statistics

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rankStats.rank", - "description": "

Rank on a given day

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rankStats.posts", - "description": "

Number of posts when this rank was achieved

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.rankStats.timestp", - "description": "

Timestamp of the rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.views", - "description": "

View count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.views_prev", - "description": "

Change in view count total

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": " {\n \"obj\": {\n \"length\": 100,\n \"data\": [\n {\n \"id\": 31475,\n \"tiktok_track_id\": \"Laxed-Siren-Beat-6804093117843770118\",\n \"name\": \"Laxed - Siren Beat\",\n \"isrc\": \"NZAM02001128\",\n \"image_url\": \"https://p16-sg.tiktokcdn.com/aweme/100x100/tos-alisg-i-0000/79fc18806b60494cb9117888b5a5f621.jpeg\",\n \"cm_track\": 29203381,\n \"tiktok_artist_names\": [\n \"Jawsh 685\"\n ],\n \"tiktok_track_ids\": [\n \"Laxed-Siren-Beat-6804093117843770118\"\n ],\n \"album_ids\": [\n 5963064\n ],\n \"album_names\": [\n \"Laxed (Siren Beat)\"\n ],\n \"album_upc\": [\n \"9420058747152\"\n ],\n \"album_label\": [\n \"Jawsh 685\"\n ],\n \"release_dates\": [\n \"2020-04-24T07:00:00.000Z\"\n ],\n \"rank\": 1,\n \"posts\": 55300000,\n \"added_at\": \"2020-10-11T07:00:00.000Z\",\n \"tiktok\": 31475,\n \"velocity\": 0,\n \"pre_rank\": 1,\n \"peak_rank\": 1,\n \"peak_date\": \"2020-08-27T07:00:00.000Z\",\n \"time_on_chart\": \"171\",\n \"rankStats\": [\n {\n \"rank\": 1,\n \"posts\": 55300000,\n \"timestp\": \"2020-10-04T07:00:00.000Z\"\n },\n {\n \"rank\": 1,\n \"posts\": 55300000,\n \"timestp\": \"2020-10-05T07:00:00.000Z\"\n },\n {\n \"rank\": 1,\n \"posts\": 55300000,\n \"timestp\": \"2020-10-06T07:00:00.000Z\"\n },\n {\n \"rank\": 1,\n \"posts\": 55300000,\n \"timestp\": \"2020-10-07T07:00:00.000Z\"\n },\n {\n \"rank\": 1,\n \"posts\": 55300000,\n \"timestp\": \"2020-10-08T07:00:00.000Z\"\n },\n {\n \"rank\": 1,\n \"posts\": 55300000,\n \"timestp\": \"2020-10-09T07:00:00.000Z\"\n },\n {\n \"rank\": 1,\n \"posts\": 55300000,\n \"timestp\": \"2020-10-10T07:00:00.000Z\"\n },\n {\n \"rank\": 1,\n \"posts\": 55300000,\n \"timestp\": \"2020-10-11T07:00:00.000Z\"\n }\n ]\n },\n ...\n ]\n } \n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Usage (tracks)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/tiktok/tracks?date=2020-10-11", - "type": "curl" - }, - { - "title": "Usage (videos)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/tiktok/videos?date=2020-10-11", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/tiktok/tracks/:chart-type", - "title": "TikTok(Tracks)", - "name": "GetTiktokTracksChartV2", - "version": "1.0.0", - "description": "

Get new TikTok Tracks Chart Information in new formats such as 'popular' and 'breakout'.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "popular", - "breakout" - ], - "optional": false, - "field": "chart-type", - "description": "

['popular': Top tracks by Popularity, 'breakout': Top tracks by Breakout]

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Chart data end date

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "weekly" - ], - "optional": true, - "field": "interval", - "defaultValue": "weekly", - "description": "

Chart Interval

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

The limit of entries to be returned.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "code2", - "description": "

Country code of the chart.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "obj", - "description": "

List of track results

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.entityId", - "description": "

Chartmetric entity ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.targetId", - "description": "

Target ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.rank", - "description": "

Current rank on chart

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "obj.chartedDate", - "description": "

Chart date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.country", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.imageUrl", - "description": "

Track image URL

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.rankDiff", - "description": "

Rank difference from previous period

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "obj.artists", - "description": "

List of artists

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.artists.careerStage", - "description": "

Artist career stage

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.artists.code2", - "description": "

Artist country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.artists.id", - "description": "

Artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.artists.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.artists.imageUrl", - "description": "

Artist image URL

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "obj.album", - "description": "

Album information

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.album.id", - "description": "

Album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.album.name", - "description": "

Album name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.album.label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "obj.album.releaseDate", - "description": "

Album release date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.externalId", - "description": "

External ID

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "obj.targetExtras", - "description": "

Additional metrics

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.airplayStreams", - "description": "

Airplay streams

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.amazonEdPlaylistCount", - "description": "

Amazon editorial playlist count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.amazonPlaylistCount", - "description": "

Amazon playlist count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.anghamiLikes", - "description": "

Anghami likes

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.anghamiPlays", - "description": "

Anghami plays

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.deezerEdPlaylistCount", - "description": "

Deezer editorial playlist count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.deezerEdPlaylistTotalReach", - "description": "

Deezer editorial playlist total reach

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.deezerPlaylistCount", - "description": "

Deezer playlist count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.deezerPlaylistTotalReach", - "description": "

Deezer playlist total reach

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.itunesEdPlaylistCount", - "description": "

iTunes editorial playlist count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.itunesPlaylistCount", - "description": "

iTunes playlist count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.pandoraLifetimeStationsAdded", - "description": "

Pandora lifetime stations added

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.pandoraLifetimeStreams", - "description": "

Pandora lifetime streams

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.shazamCount", - "description": "

Shazam count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.spotifyEdPlaylistCount", - "description": "

Spotify editorial playlist count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.spotifyEdPlaylistTotalReach", - "description": "

Spotify editorial playlist total reach

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.spotifyPlaylistCount", - "description": "

Spotify playlist count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.spotifyPlaylistTotalReach", - "description": "

Spotify playlist total reach

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.spotifyPlays", - "description": "

Spotify plays

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.spotifyPopularity", - "description": "

Spotify popularity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.tiktokTopVideosComments", - "description": "

TikTok video comments

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.tiktokTopVideosLikes", - "description": "

TikTok video likes

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.tiktokTopVideosViews", - "description": "

TikTok video views

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.youtubeComments", - "description": "

YouTube comments

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.youtubeLikes", - "description": "

YouTube likes

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.targetExtras.youtubeViews", - "description": "

YouTube views

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.genre", - "description": "

Track genre

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "obj.isChartGenre", - "description": "

Whether the genre is a chart genre

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": [\n {\n \"entityId\": 15818551,\n \"targetId\": \"2212754\",\n \"name\": \"My Boo - Hitman's Club Mix\",\n \"rank\": 1,\n \"chartedDate\": \"2025-05-05\",\n \"country\": \"US\",\n \"imageUrl\": \"https://i.scdn.co/image/df8c9d9318a5f598dba02de84323d97656d5adbe\",\n \"rankDiff\": 1,\n \"artists\": [\n {\n \"careerStage\": \"mainstream\",\n \"code2\": \"US\",\n \"id\": 209688,\n \"name\": \"Ghost Town DJs\",\n \"imageUrl\": \"https://i.scdn.co/image/ab6761610000e5eb156aef8c73890e10b60e94d4\"\n }\n ],\n \"album\": {\n \"id\": 903594,\n \"name\": \"My Boo (Hitman's Club Mix)\",\n \"label\": \"Columbia\",\n \"releaseDate\": \"1996-03-26T00:00:00.000Z\"\n },\n \"externalId\": \"My-Boo-Hitman's-Club-Mix-6716386670813399041\",\n \"targetExtras\": {\n \"airplayStreams\": 35174,\n \"amazonEdPlaylistCount\": 8,\n \"amazonPlaylistCount\": 8,\n \"anghamiLikes\": 18,\n \"anghamiPlays\": 181,\n \"deezerEdPlaylistCount\": 1,\n \"deezerEdPlaylistTotalReach\": 243270,\n \"deezerPlaylistCount\": 18,\n \"deezerPlaylistTotalReach\": 383210,\n \"itunesEdPlaylistCount\": 42,\n \"itunesPlaylistCount\": 74,\n \"pandoraLifetimeStationsAdded\": 139078,\n \"pandoraLifetimeStreams\": 171200616,\n \"shazamCount\": 3813848,\n \"spotifyEdPlaylistCount\": 12,\n \"spotifyEdPlaylistTotalReach\": 1595455,\n \"spotifyPlaylistCount\": 45110,\n \"spotifyPlaylistTotalReach\": 13762081,\n \"spotifyPlays\": 160344152,\n \"spotifyPopularity\": 68,\n \"tiktokTopVideosComments\": 103657,\n \"tiktokTopVideosLikes\": 10195523,\n \"tiktokTopVideosViews\": 79470645,\n \"youtubeComments\": 1116,\n \"youtubeLikes\": 430442,\n \"youtubeViews\": 32729184\n },\n \"genre\": \"r&b/soul\",\n \"isChartGenre\": false\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/tiktok/tracks/breakout?code2=US&latest=true&limit=1", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/twitch/users", - "title": "Twitch", - "name": "GetTwitchChart", - "version": "1.0.0", - "description": "

Get data insights for users on Twitch Charts, including latest position, time on chart and position changes over time.

This endpoint is useful for identifying the music trends on Twitch Charts.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "date", - "description": "

Charting date (defaulted to the latest chart).

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "since", - "description": "

Start of date range, in ISO date format (e.g. "2017-03-25").

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "limit", - "description": "

The limit of entries to be returned (max 500).

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "daily", - "weekly", - "monthly" - ], - "optional": false, - "field": "duration", - "description": "" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "followers", - "viewer_hours" - ], - "optional": false, - "field": "type", - "description": "

The type of chart

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of the since date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Number of data entries.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

List of results.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

Twitch user name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.image_url", - "description": "

Artist image url.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.link", - "description": "

Twitch link.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.cm_artist", - "description": "

Chartmetric artist id.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rank", - "description": "

Current rank.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.followers", - "description": "

Followers.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.added_at", - "description": "

Added date.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.twitch_user", - "description": "

Twitch user.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.velocity", - "description": "

Velocity.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.pre_rank", - "description": "

Previous rank.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.peak_rank", - "description": "

Top Rank.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.peak_date", - "description": "

Date of Top Rank.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.time_on_chart", - "description": "

Time on the chart.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.rankStats", - "description": "

Rank object containing rank statistics.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rankStats.rank", - "description": "

Rank at a given date.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rankStats.followers", - "description": "

Number of followers at a given date.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.rankStats.timestp", - "description": "

time stamp.

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"length\": 500,\n \"data\": [\n {\n \"name\": \"ibai\",\n \"image_url\": \"https://static-cdn.jtvnw.net/jtv_user_pictures/ec48092c-b628-43ba-beba-c8bcd9a93354-profile_image-300x300.png\",\n \"link\": \"https://twitch.tv/ibai\",\n \"cm_artist\": null,\n \"rank\": 1,\n \"followers\": 2238200,\n \"added_at\": \"2020-09-09T07:00:00.000Z\",\n \"twitch_user\": 9416,\n \"velocity\": 0,\n \"pre_rank\": 1,\n \"peak_rank\": 1,\n \"peak_date\": \"2020-07-08T07:00:00.000Z\",\n \"time_on_chart\": \"63\",\n \"rankStats\": [\n {\n \"rank\": 1,\n \"followers\": 2212444,\n \"timestp\": \"2020-09-08T07:00:00.000Z\"\n },\n {\n \"rank\": 1,\n \"followers\": 2238200,\n \"timestp\": \"2020-09-09T07:00:00.000Z\"\n }\n ]\n },\n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Usage (users)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/twitch/users?since=2020-09-09&type=followers&duration=daily", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/youtube/:chart_type", - "title": "YouTube", - "name": "GetYoutubeChart", - "version": "1.0.0", - "description": "

Get data insights for tracks, albums or videos on iTunes Charts, including latest position, time on chart and position changes over time.

Given the appropriate filters, get data specific to country.

This endpoint is useful for identifying the trends on YouTube Charts in different countries.

This data is updated weekly only (on Thursdays).

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "tracks", - "artists", - "trends", - "videos" - ], - "optional": false, - "field": "chart_type", - "description": "" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "US", - "JP", - "..." - ], - "optional": false, - "field": "country_code", - "description": "

Country Code (Full list in Introduction)

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Charting date. Data is updated weekly on Thursday on a week delay (for example: 10/22 data is added on 10/28). Since the data is only available once a week you can change offset value to paginate.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Number of entries.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

List of results.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.id", - "description": "

YouTube ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

Track name.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.isrc", - "description": "

Track isrc.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.youtube_artist", - "description": "

YouTube artist link.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.image_url", - "description": "

image url.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.cm_track", - "description": "

Chartmetric track id.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.upload_date", - "description": "

Upload date.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.license", - "description": "

License.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_names", - "description": "

Album names.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.youtube_artist_names", - "description": "

YouTube artist names.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.cm_artist", - "description": "

Chartmetric artist id

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_names", - "description": "

Artist names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.code2s", - "description": "

Country codes

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_images", - "description": "

Artist images

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_covers", - "description": "

Artist covers

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.youtube_artist_ids", - "description": "

YouTube artist IDs.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.youtube_track_ids", - "description": "

YouTube track IDs.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.youtube_track_id", - "description": "

YouTube track ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.artist_name", - "description": "

Artist name.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.position", - "description": "

Current position.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.added_at", - "description": "

Added date.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.view_count", - "description": "

Views count.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.type", - "description": "

desc.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.velocity", - "description": "

Velocity.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.pre_rank", - "description": "

Previous rank.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.peak_rank", - "description": "

Top rank.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.peak_date", - "description": "

Date of top rank.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.time_on_chart", - "description": "

Track time on chart.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.rankStats", - "description": "

Track rank history.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rankStats.rank", - "description": "

Rank of track at a given date.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.rankStats.timestp", - "description": "

Rank time stamp.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.viewStats", - "description": "

Views rank history.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.viewStats.views", - "description": "

Views count.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.viewStats.timestp", - "description": "

View count time stamp.

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": {\n \"length\": 100,\n \"data\": [\n {\n \"id\": \"uLHqpjW3aDs\",\n \"name\": \"Roddy Ricch - The Box [Official Audio]\",\n \"isrc\": \"USAT21906978\",\n \"youtube_artist\": \"/g/11fhvxdh1f\",\n \"image_url\": null,\n \"cm_track\": 27599255,\n \"upload_date\": \"2019-12-06T05:05:28.000Z\",\n \"license\": \"WMG (on behalf of Atlantic Records); CMRRA, Kobalt Music Publishing, LatinAutor, PEDL, Beyondbond, Inc. (Publishing), Warner Chappell, UNIAO BRASILEIRA DE EDITORAS DE MUSICA - UBEM, Peermusic, BMI - Broadcast Music Inc., LatinAutor - PeerMusic, LatinAutor - Warner Chappell, and 14 Music Rights Societies\",\n \"album_names\": [\n \"The Box\"\n ],\n \"youtube_artist_names\": [\n \"Roddy Ricch\"\n ],\n \"cm_artist\": [\n 839964\n ],\n \"artist_names\": [\n \"Roddy Ricch\"\n ],\n \"code2s\": [\n \"US\"\n ],\n \"artist_images\": [\n \"https://lh3.googleusercontent.com/a-/AAuE7mD9mk4b9HXoY3SQHbXScUGwoLs_pDjXrA_rG3KP=w60-h60-l90-rj\"\n ],\n \"artist_covers\": [\n null\n ],\n \"youtube_artist_ids\": [\n \"/g/11fhvxdh1f\"\n ],\n \"youtube_track_ids\": [\n \"uLHqpjW3aDs\"\n ],\n \"youtube_track_id\": \"uLHqpjW3aDs\",\n \"artist_name\": \"Roddy Ricch\",\n \"position\": 1,\n \"added_at\": \"2020-02-06T00:00:00.000Z\",\n \"view_count\": 20500000,\n \"raw_data\": null,\n \"velocity\": 0,\n \"pre_rank\": 1,\n \"peak_rank\": 1,\n \"peak_date\": \"2020-01-09T00:00:00.000Z\",\n \"time_on_chart\": \"8\",\n \"rankStats\": [\n {\n \"rank\": 1,\n \"timestp\": \"2020-01-30T00:00:00.000Z\"\n }, ...\n ],\n \"viewStats\": [\n {\n \"views\": 18400000,\n \"timestp\": \"2020-01-30T00:00:00.000Z\"\n }, ...\n ]\n }, ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Usage (tracks)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/youtube/tracks?date=2020-02-06&country_code=us&offset=0", - "type": "curl" - }, - { - "title": "Usage (artists)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/youtube/artists?date=2020-02-06&country_code=us&offset=0", - "type": "curl" - }, - { - "title": "Usage (trends)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/youtube/trends?date=2020-02-06&country_code=us&offset=0", - "type": "curl" - }, - { - "title": "Usage (videos)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/youtube/videos?date=2020-02-06&country_code=us&offset=0", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/charts/itunes/:chart-type", - "title": "iTunes", - "name": "GetiTunesChart", - "version": "1.0.0", - "description": "

Get data insights for tracks, albums or videos on iTunes Charts, including latest position, time on chart and position changes over time.

Given the appropriate filters, get data specific to country, genre and type depending on the chart type.

This endpoint is useful for identifying the music trends on iTunes Charts across genres in different countries.

", - "group": "Charts", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "tracks", - "albums", - "videos" - ], - "optional": false, - "field": "chart-type", - "description": "" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "US", - "JP", - "..." - ], - "optional": false, - "field": "country_code", - "description": "

Country Code (Full list in Introduction)

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Charting Date

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "All Genres", - "Pop", - "Rock", - "Dance", - "Latino", - "K-Pop", - "Singer/Songwriter", - "Hip-Hop/Rap", - "Jazz", - "Electronic", - "R&B/Soul", - "Blues", - "Country", - "Reggae", - "Classical", - "Alternative", - "World", - "Disney", - "J-Pop", - "Christian & Gospel", - "Easy", - "Listening", - "Childrens", - "Music", - "Fitness & Workout", - "Soundtrack" - ], - "optional": false, - "field": "genre", - "description": "

(tracks and albums only)

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Each request returns a max limit of entries. To get full dataset, use offset option.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Number of entries

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

List of results

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.isrc", - "description": "

Track isrc

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_album_id", - "description": "

iTunes album id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.composer_name", - "description": "

Composer names

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.cm_track", - "description": "

Chartmetric track id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.track_genre", - "description": "

Track genre

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.cm_artist", - "description": "

Chartmetric artist id

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_names", - "description": "

Artist names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.code2s", - "description": "

Country codes

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_images", - "description": "

Artist images

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_covers", - "description": "

Artist covers

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_artist_ids", - "description": "

iTunes artist ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_artist_names", - "description": "

iTunes artist names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_track_ids", - "description": "

iTunes track ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_album_ids", - "description": "

iTunes album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.storefronts", - "description": "

Storefronts

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_ids", - "description": "

Chartmetric album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_names", - "description": "

Album names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_upc", - "description": "

Album upc

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_label", - "description": "

Album labels

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.release_dates", - "description": "

Release dates

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rank", - "description": "

Current rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.added_at", - "description": "

Added date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.code2", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.genre", - "description": "

genre

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.itunes", - "description": "

iTunes

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.velocity", - "description": "

Velocity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.pre_rank", - "description": "

Previous rank

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.peak_rank", - "description": "

Top rank

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.peak_date", - "description": "

Date of top rank

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.time_on_chart", - "description": "

Track time on chart

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.rankStats", - "description": "

Track rank history

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rankStats.rank", - "description": "

Rank of the track at a given time.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.rankStats.timestp", - "description": "

Time stamp of the rank

" - } - ] - }, - "examples": [ - { - "title": "Example Response:", - "content": "{\n \"obj\": {\n \"length\": 200,\n \"data\": [\n {\n \"id\": 78333,\n \"name\": \"Thriller\",\n \"isrc\": \"USSM19902989\",\n \"itunes_album_id\": [\n \"269572838\"\n ],\n \"composer_name\": null,\n \"image_url\": \"http://is4.mzstatic.com/image/thumb/Features/v4/57/9a/d2/579ad2e5-e9eb-630a-dafd-1704e0fbe9cc/mza_1011095246346662165.jpg/170x170bb-85.jpg\",\n \"cm_track\": 15823679,\n \"track_genre\": \"R&B/Soul\",\n \"cm_artist\": [\n 178\n ],\n \"artist_names\": [\n \"Michael Jackson\"\n ],\n \"code2s\": [\n \"US\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/51dad9aaabe5643818840207a9a8957c2ad91bf2\"\n ],\n \"artist_covers\": [\n null\n ],\n \"itunes_artist_ids\": [\n 32940\n ],\n \"itunes_artist_names\": [\n \"Michael Jackson\"\n ],\n \"itunes_track_ids\": [\n \"269573303\"\n ],\n \"itunes_album_ids\": [\n \"269572838\"\n ],\n \"storefronts\": [\n \"US\"\n ],\n \"album_ids\": [\n 3952604\n ],\n \"album_names\": [\n \"Thriller\"\n ],\n \"album_upc\": [\n \"074643811224\"\n ],\n \"album_label\": [\n \"℗ 1982 MJJ Productions Inc.\"\n ],\n \"release_dates\": [\n \"1982-11-30T00:00:00.000Z\"\n ],\n \"rank\": 1,\n \"added_at\": \"2018-11-01T00:00:00.000Z\",\n \"code2\": \"US\",\n \"genre\": \"Pop\",\n \"itunes\": 78333,\n \"velocity\": 2,\n \"pre_rank\": 1,\n \"peak_rank\": 1,\n \"peak_date\": \"2020-11-01T00:00:00.000Z\",\n \"time_on_chart\": \"394\",\n \"rankStats\": [\n {\n \"rank\": 15,\n \"timestp\": \"2018-10-25T00:00:00.000Z\"\n },\n {\n \"rank\": 15,\n \"timestp\": \"2018-10-26T00:00:00.000Z\"\n }, ...\n ]\n }, ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Usage (tracks)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/itunes/tracks?date=2018-11-01&country_code=US&genre=Pop&offset=0", - "type": "curl" - }, - { - "title": "Usage (albums)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/itunes/albums?date=2018-11-01&country_code=US&genre=Pop&offset=0", - "type": "curl" - }, - { - "title": "Usage (videos)", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/charts/itunes/videos?date=2018-11-01&country_code=US&offset=0", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/charts.docs.ts", - "groupTitle": "Charts" - }, - { - "type": "get", - "url": "/api/city/:id/:source/top-artists", - "title": "Top Artists", - "version": "1.1.0", - "name": "GetTopArtists", - "description": "

Get top 100 artists with maximum count for source in the city

", - "group": "City", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric city id

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "youtube", - "shazam", - "radio", - "instagram" - ], - "optional": false, - "field": "source", - "description": "

The platform to get top artists for

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "count", - "description": "

Represents local Spotify monthly listeners for Spotify, local daily views for YouTube, local followers for Instagram, local occurrences for Shazam and Radio.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Artist image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isni", - "description": "

isni of Artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "code2", - "description": "

2-letter Artist country code (Full List in Introduction)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "hometown_city", - "description": "

hometown city of Artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "current_city", - "description": "

current city of Artist

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_followers", - "description": "

Number of Spotify followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_popularity", - "description": "

Number of Spotify popularity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "sp_monthly_listeners", - "description": "

Number of Spotify monthly listeners

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "deezer_fans", - "description": "

Number of Deezer fans

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_artist_rank", - "description": "

Chartmetric artist rank

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "tags", - "description": "

Tags

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_artist_ids", - "description": "

Spotify artist IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "itunes_artist_ids", - "description": "

iTunes Artist IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "deezer_artist_ids", - "description": "

Deezer Artist IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "amazon_artist_ids", - "description": "

Amazon Artist IDs

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "latest_release", - "description": "

Latest album release info

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "latest_release.cm_album", - "description": "

Album IDs

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "latest_release.album_name", - "description": "

Album name

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "latest_release.release_date", - "description": "

Album release date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "latest_release.upc", - "description": "

Album UPC

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "latest_release.label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "latest_release.image_url", - "description": "

Album image url

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"count\": \"258\",\n \"id\": 1401297,\n \"name\": \"Pop Smoke\",\n \"image_url\": \"https://i.scdn.co/image/8f0a45ff4868c7868a7996b57da64f2e89042e26\",\n \"isni\": null,\n \"code2\": \"us\",\n \"hometown_city\": null,\n \"current_city\": null,\n \"sp_followers\": 1374300,\n \"sp_popularity\": 95,\n \"sp_monthly_listeners\": 27687238,\n \"deezer_fans\": 181349,\n \"tags\": [\n \"rap\"\n ],\n \"spotify_artist_ids\": [\n \"0eDvMgVFoNV3TpwtrVCoTj\"\n ],\n \"itunes_artist_ids\": [\n 1450601383\n ],\n \"deezer_artist_ids\": [\n \"58447102\"\n ],\n \"cm_artist_rank\": 37,\n \"amazon_artist_ids\": [\n \"B07N6R3268\"\n ],\n \"latest_release\": {\n \"cm_album\": 6591557,\n \"album_name\": \"今は亡きあの人たち\",\n \"release_date\": \"2020-08-19T07:00:00.000Z\",\n \"upc\": \"602435142609\",\n \"label\": \"UMG Recordings, Inc.\",\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b27349066d2258ad2e46955e2381\"\n }\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer ACCESS_KEY' https://api.chartmetric.com/api/city/7060/spotify/top-artists", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/city.docs.ts", - "groupTitle": "City" - }, - { - "type": "get", - "url": "/api/city/:id/:source/top-tracks", - "title": "Top Tracks", - "version": "1.1.0", - "name": "GetTopTracks", - "description": "

Get top 100 tracks with maximum count for source in the city

", - "group": "City", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric city id

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "youtube", - "radio", - "shazam" - ], - "optional": false, - "field": "source", - "description": "

The platform to get top tracks for

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isrc", - "description": "

Track ISRC

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "count", - "description": "

Represents local daily views for YouTube, local occurrences for Shazam and Radio

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "position_stats", - "description": "

Position stats of the track

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "position_stats.timestp", - "description": "

Track position timestamp

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "position_stats.position", - "description": "

Track position

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Track ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Track image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "description", - "description": "

Track description

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "created_at", - "description": "

Track created at

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "modified_at", - "description": "

Track modified at

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tags", - "description": "

Tags

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_track", - "description": "

Chartmetric Track ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "cm_artist", - "description": "

Chartmetric Artist ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist_names", - "description": "

Artist names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "code2s", - "description": "

2-letter country codes

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist_images", - "description": "

Artist image

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist_covers", - "description": "

Artist covers

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_track_ids", - "description": "

Spotify track IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_album_ids", - "description": "

Spotify album IDs

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "spotify_duration_ms", - "description": "

Spotify duration in milliseconds

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "itunes_track_ids", - "description": "

iTunes track IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "itunes_album_ids", - "description": "

iTunes album IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "storefronts", - "description": "

2-letter country codes of storefronts

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "deezer_track_ids", - "description": "

Deezer track IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "deezer_album_ids", - "description": "

Deezer album IDs

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "deezer_duration", - "description": "

Deezer track duration in seconds

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "amazon_track_ids", - "description": "

Amazon track IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "amazon_album_ids", - "description": "

Amazon album IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_ids", - "description": "

Album IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_names", - "description": "

Album names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_upc", - "description": "

Album UPC's

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_label", - "description": "

Album labels

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "release_dates", - "description": "

Album release dates

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n {\n \"obj\": [\n {\n \"isrc\": \"DEA629252500\",\n \"count\": \"28\",\n \"position_stats\": [\n {\n \"timestp\": \"2020-08-03\",\n \"position\": 81\n },\n {\n \"timestp\": \"2020-08-04\",\n \"position\": 58\n },\n {\n \"timestp\": \"2020-08-05\",\n \"position\": 41\n }, ...\n ],\n \"id\": 11291890,\n \"name\": \"Major Tom\",\n \"image_url\": \"https://i.scdn.co/image/73b7f1f27769379d6796ed9300bda12d9dd3e75d\",\n \"description\": null,\n \"created_at\": null,\n \"modified_at\": \"2020-07-03T05:29:55.573Z\",\n \"tags\": \"Pop,Soundtrack,Soundtrack,Music,Pop\",\n \"cm_track\": 11291890,\n \"cm_artist\": [\n 217598\n ],\n \"artist_names\": [\n \"Peter Schilling\"\n ],\n \"code2s\": [\n \"DE\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/3f6870676468c6f14dff7df4a7a0cc57c604e258\"\n ],\n \"artist_covers\": [\n null\n ],\n \"spotify_track_ids\": [\n \"6lXKNdOsnaLv9LwulZbxNl\",\n \"4SddoTPyGG1Kv8pYFGRw7i\",\n \"1m8Xeq5ZDBX572XJjtoDYq\",\n \"0XbIyKQ3R14jfujso9FtGD\",\n \"40b7v1pR4AeeIesdeouVbI\",\n \"1zHYDGRJsGY0pg6aapBUwa\",\n \"41grRkO3XmFylQUWAEMNLs\",\n \"7uORtV6Vw12DUiS38U2Jan\",\n \"4WWcBgWRngXPj2JgGC6DaA\",\n \"27LV69d5KKG27Dn31Rm0pY\",\n \"2wEZPGDPViaHWtPEO31A9L\",\n \"0JC6ra7f64oDlUzTpYMtDb\"\n ],\n \"spotify_album_ids\": [\n \"1rYXSHYaxxQ9HclnAeVhxv\",\n \"0hr0QFvloj2VSZYg9E3bFj\",\n \"4xYHZA1NhxQAcc9JvfsMX1\",\n \"66arz2b02iYS40vJ4w0fKf\",\n \"50o76VpuaD6KR4VQqG7KIr\",\n \"1WNo1GGuxmls0tNiukSTnB\",\n \"18TXRFXRdy8ThD1PEWQoN3\",\n \"5Oyqflkptj0439nD1WkPxm\",\n \"0zPUfMqIUwToTHyUzK7GUx\",\n \"0NGAu5H3CctGwcgyZwjgua\",\n \"4SwYopbqPzWFKspSI5R0p5\",\n \"2ubLx1k0weu2ZjMXDI0Do2\"\n ],\n \"spotify_duration_ms\": 300266,\n \"itunes_track_ids\": [\n \"40633230\",\n \"298674756\",\n \"1260644262\",\n \"321972589\",\n \"1020525890\",\n \"1235468429\"\n ],\n \"itunes_album_ids\": [\n \"40633214\",\n \"298674755\",\n \"1260643896\",\n \"321972510\",\n \"1020525877\",\n \"1235468416\"\n ],\n \"storefronts\": [\n \"PE\",\n \"US\",\n \"US\",\n \"US\",\n \"US\",\n \"AU\"\n ],\n \"deezer_track_ids\": [\n \"520450102\",\n \"3605281\"\n ],\n \"deezer_album_ids\": [\n 67074482,\n 340212\n ],\n \"deezer_duration\": 299,\n \"amazon_track_ids\": null,\n \"amazon_album_ids\": null,\n \"album_ids\": [\n 473527,\n 502054,\n 713710,\n 1064261,\n 5104718,\n 5147720,\n 5150479,\n 5154498,\n 5391653,\n 5648394,\n 5790083,\n 5818607,\n 5964785\n ],\n \"album_names\": [\n \"SWR1 - Achtzig 80er\",\n \"WDR - Die beliebtesten NDW-Hits\",\n \"80's Top 40 Hits\",\n \"Von Anfang An...Bis Jetzt !\",\n \"De store fra 80'erne\",\n \"Hits Of The 80s\",\n \"Top 100 80s\",\n \"Sommermusik\",\n \"Hits zum mitsingen\",\n \"Otra dimensión: La nueva ola alrededor del mundo\",\n \"Club Classics: 80's Electronic\",\n \"Home Office Pop Hits\",\n \"80's Work Out\"\n ],\n \"album_upc\": [\n \"5099969886652\",\n \"4029759074533\",\n \"825646550708\",\n \"639842921060\",\n \"842474183977\",\n \"190295446765\",\n \"190295362485\",\n \"842474195208\",\n \"842474148341\",\n \"5059460004627\",\n \"5059460012455\",\n \"5059460020016\",\n \"5059460021846\"\n ],\n \"album_label\": [\n \"EMM\",\n \"edel entertainment GmbH\",\n \"WM Mexico\",\n \"WM Germany\",\n \"Warner Music Group - X5 Music Group\",\n \"Rhino\",\n \"Rhino\",\n \"Warner Music Group - X5 Music Group\",\n \"Warner Music Group - X5 Music Group\",\n \"Warner Music Group - X5 Music Group\",\n \"Warner Music Group - X5 Music Group\",\n \"Warner Music Group - X5 Music Group\",\n \"Warner Music Group - X5 Music Group\"\n ],\n \"release_dates\": [\n \"2009-06-19T07:00:00.000Z\",\n \"2012-01-06T08:00:00.000Z\",\n \"2012-09-18T07:00:00.000Z\",\n \"1983-06-27T07:00:00.000Z\",\n \"2018-07-02T07:00:00.000Z\",\n \"2019-03-29T07:00:00.000Z\",\n \"2019-08-30T07:00:00.000Z\",\n \"2019-03-15T07:00:00.000Z\",\n \"2019-11-22T08:00:00.000Z\",\n \"2020-01-31T08:00:00.000Z\",\n \"2020-03-13T07:00:00.000Z\",\n \"2020-03-21T07:00:00.000Z\",\n \"2020-04-27T07:00:00.000Z\"\n ]\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer ACCESS_KEY' https://api.chartmetric.com/api/city/7060/shazam/top-tracks", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/city.docs.ts", - "groupTitle": "City" - }, - { - "type": "get", - "url": "/api/curator/:platform/:id/", - "title": "Metadata", - "name": "GetCuratorMetadata", - "version": "1.0.0", - "group": "Curator", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric curator ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "applemusic", - "deezer" - ], - "optional": false, - "field": "platform", - "description": "" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric curator ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "user_id", - "description": "

Spotify ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Curator name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "submithub_id", - "description": "

submithub_id
Note: This field is deprecated and it's hard-coded to null. It will be removed after 03-31-2024.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "last_updated", - "description": "

Curator last update

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "cm_statistics", - "description": "

Chartmetric statistics

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_sp_playlists", - "description": "

Number of Spotify playlists

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.sp_followers", - "description": "

Number of Spotify followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.max_sp_playlist_followers", - "description": "

Max number of Spotify playlist followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.sp_playlist_total_reach", - "description": "

Spotify playlist total reach

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "tags", - "description": "

tags

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "tags.id", - "description": "

Tag id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tags.name", - "description": "

Tag name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "instagram_followers", - "description": "

Instagram followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "soundcloud_followers", - "description": "

Soundcloud followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "facebook_fans", - "description": "

Facebook fans

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "facebook_followers", - "description": "

Facebook Followers

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "youtube_views", - "description": "

YouTube views

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "youtube_subscribers", - "description": "

YouTube subscribers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "twitter_followers", - "description": "

Twitter followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "twitter_retweets", - "description": "

Twitter retweets

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tiktok_likes", - "description": "

TikTok likes

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "tiktok_followers", - "description": "

TikTok followers

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": " HTTP/1.1 200 OK\n{\n \"obj\": {\n \"id\": 2,\n \"user_id\": \"spotify\",\n \"name\": \"Spotify\",\n \"image_url\": \"https://i.scdn.co/image/ab6775700000ee8555c25988a6ac314394d3fbf5\",\n \"submithub_id\": null,\n \"last_updated\": \"2021-05-06T07:00:00.000Z\",\n \"cm_statistics\": {\n \"num_sp_playlists\": 70967,\n \"sp_followers\": 9563979,\n \"max_sp_playlist_followers\": 27702406,\n \"sp_playlist_total_reach\": 2082364476\n },\n \"tags\": [\n {\n \"id\": 103,\n \"name\": \"folk\"\n },\n {\n \"id\": 112,\n \"name\": \"rock\"\n },\n {\n \"id\": 114,\n \"name\": \"classical\"\n }\n ],\n \"instagram_followers\": 6622570,\n \"soundcloud_followers\": null,\n \"facebook_fans\": 22691516,\n \"facebook_followers\": 22772732,\n \"youtube_views\": \"552256171\",\n \"youtube_subscribers\": 1200000,\n \"twitter_followers\": 4181216,\n \"twitter_retweets\": 7110,\n \"tiktok_likes\": \"834600\",\n \"tiktok_followers\": 199000\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/curator/spotify/2/", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/curator.docs.ts", - "groupTitle": "Curator" - }, - { - "type": "get", - "url": "/api/curator/:platform/:id/playlists", - "title": "Playlists", - "name": "GetCuratorPlaylists", - "description": "

Get playlists created by a curator.

Each platform has different sorting options due to the variety of data available. Valid combinations are provided in the below table for your convenience:

platform Valid sortColumn values
spotify last_updated, followers (default), num_track
applemusic last_updated (default), num_track
deezer last_updated (default), followers, num_track
", - "version": "1.0.0", - "group": "Curator", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric curator ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "applemusic", - "deezer" - ], - "optional": false, - "field": "platform", - "description": "" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

The number of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The page number you want to access

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "sortColumn", - "description": "

Sort by column. See table above for allowed values

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "sortOrderDesc", - "description": "

Whether to sort in descending order. Can only be defined when sortColumn is defined

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "total_length", - "description": "

Total number of records

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Length of the data array

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

Data array

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.id", - "description": "

Chartmetric Playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

Playlist Name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.playlist_id", - "description": "

Platform Playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.description", - "description": "

Playlist description

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.image_url", - "description": "

Playlist image url

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.sys_last_updated", - "description": "

Playlist sys last updated time

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.last_updated", - "description": "

Playlist last updated time

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "data.personalized", - "description": "

Playlist personalized or not

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.code2", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.owner_name", - "description": "

Playlist owner name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.owner_id", - "description": "

Playlist owner id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.user_id", - "description": "

Playlist owner id

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "data.official", - "description": "

Playlist official or now

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.tags", - "description": "

Playlist tags

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.tags.id", - "description": "

Playlist tag id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.tags.name", - "description": "

Playlist tag name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.followers", - "description": "

Playlist followers

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.num_track", - "description": "

Playlist number of tracks

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.fdiff_week", - "description": "

Playlist followers change by week from current date

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.fdiff_month", - "description": "

Playlist followers change by month from current date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.catalog", - "description": "

Catalog

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.active_ratio", - "description": "

Active ratio

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"total_length\": \"134\",\n \"length\": 50,\n \"data\": [\n {\n \"id\": 1667152,\n \"name\": \"Dominican Republic Viral 50\",\n \"playlist_id\": \"37i9dQZEVXbJWZV7aRNQck\",\n \"description\": \"Your daily update of the most viral tracks in Dominican Republic.\",\n \"image_url\": \"https://charts-images.scdn.co/assets/locale_en/viral/daily/region_do_large.jpg\",\n \"sys_last_updated\": \"2020-11-04T09:17:40.488Z\",\n \"last_updated\": \"2020-11-04T09:17:39.956Z\",\n \"personalized\": false,\n \"code2\": \"do\",\n \"owner_name\": \"spotifycharts\",\n \"owner_id\": 133000,\n \"user_id\": \"spotifycharts\",\n \"official\": true,\n \"tags\": [\n {\n \"id\": 4280,\n \"name\": \"latin\"\n }\n ],\n \"followers\": 23896,\n \"num_track\": \"50\",\n \"fdiff_week\": 43,\n \"fdiff_month\": 322,\n \"catalog\": \"frontline \",\n \"active_ratio\": 0\n }, ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/curator/spotify/133000/playlists?limit=50&offset=0", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/curator.docs.ts", - "groupTitle": "Curator" - }, - { - "type": "get", - "url": "/api/curator/:platform/:id/stat/:source", - "title": "Fan Metrics", - "version": "1.0.0", - "name": "GetCuratorStat", - "description": "

Get curator's fan metrics for various services.

There are two ways to get fan metrics from each source:

1. Return all fields (by default)
2. Return only selected field (by adding 'field' parameter)

For each source, here are the respective fields you can query for:

SOURCE VALID FIELDS
spotify followers (platform=spotify only)
facebook followers, likes, talks
twitter followers
youtube_channel subscribers, views, videos
instagram followers
tiktok followers, likes
If using the field parameter, ensure that the field is valid with respect to the source using the above table.

For example, /spotify?field=followers is valid, while /youtube_channel?field=followers is invalid.

Note that The API response may not return all the data points between the two dates when we are missing the data. They may still show on our UI because we are interpolating the missing data between the two existing data points. See the interpolated: true|false in the response when using the interpolated parameter.", - "group": "Curator", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "applemusic", - "itunes", - "deezer" - ], - "optional": false, - "field": "platform", - "description": "

Streaming platform type (Note: itunes and applemusic return the same data)

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "facebook", - "twitter", - "youtube_channel", - "instagram", - "tiktok" - ], - "optional": false, - "field": "source", - "description": "

Data source

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "since", - "defaultValue": "20 days ago", - "description": "

begining date, in ISO date format (e.g. "2017-03-25").

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

end date, in ISO date format (e.g. "2017-03-25"). Default is today.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "field", - "description": "

Specific stat from source (followers, popularity, listeners, talks, subscribers, etc.) See above table for valid values per source.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "defaultValue": "false", - "description": "

If set to true, returns latest data point available regardless of date. If this is true, since/until parameters will be ignored

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "interpolated", - "defaultValue": "false", - "description": "

Returns interpolated data for missing data. We use a very simple logic for this - by filling the data with the average difference between the two days.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "link", - "description": "

Link associated with the curator for the specified domain if present

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "followers", - "description": "

Statistics on followers

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "popularity", - "description": "

Statistics on popularity

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "listeners", - "description": "

Statistics on listeners

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "followers_to_listeners_ratio", - "description": "

Statistics on the differences between followers and listeners

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "value", - "description": "

Value of metric requested

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "weekly_diff", - "description": "

The value difference between today and 7 days ago for the field.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "weekly_diff_percent", - "description": "

The percentage difference between today and 7 days ago for the field.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "monthly_diff", - "description": "

The value difference between today and 28 days ago for the field.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "monthly_diff_percent", - "description": "

The percentage difference between today and 28 days ago for the field.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "daily_diff", - "description": "

This field is called "diff" if interpolated is set to false. Daily Difference in Value (not applicable for Spotify popularity)

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "timestp", - "description": "

The time this statistic was achieved in ISO format.

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "interpolated", - "description": "

False by default. If true, this value did not come straight from the source, but was rather interpolated based on the previous/next values (not applicable for Spotify popularity and Wikipedia views). Only present if interpolated=true in the query parameters of the request.

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "flags", - "description": "

Indicates whether a post was made on the given source on this date. For example, if flag=true for a source=facebook request there was a new post on Facebook on that day which could help explain increases in related statistics.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "diff", - "description": "

The value difference between this data point and the previous data point in the response. Always null for first object in a result array.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response (All Fields Usage):", - "content": " HTTP/1.1 200 OK\n {\n \"obj\": {\n \"followers\": [\n {\n \"weekly_diff\": null,\n \"weekly_diff_percent\": null,\n \"monthly_diff\": null,\n \"monthly_diff_percent\": null,\n \"value\": 462391,\n \"timestp\": \"2022-02-06T00:00:00.000Z\",\n \"flags\": false,\n \"diff\": null\n },\n {\n \"weekly_diff\": null,\n \"weekly_diff_percent\": null,\n \"monthly_diff\": null,\n \"monthly_diff_percent\": null,\n \"value\": 464773,\n \"timestp\": \"2022-02-16T00:00:00.000Z\",\n \"flags\": false,\n \"diff\": 2382\n }\n ],\n \"likes\": [\n {\n \"weekly_diff\": null,\n \"weekly_diff_percent\": null,\n \"monthly_diff\": null,\n \"monthly_diff_percent\": null,\n \"value\": 462391,\n \"timestp\": \"2022-02-06T00:00:00.000Z\",\n \"flags\": false,\n \"diff\": null\n },\n {\n \"weekly_diff\": null,\n \"weekly_diff_percent\": null,\n \"monthly_diff\": null,\n \"monthly_diff_percent\": null,\n \"value\": 464773,\n \"timestp\": \"2022-02-16T00:00:00.000Z\",\n \"flags\": false,\n \"diff\": 2382\n }\n ],\n \"talks\": [\n {\n \"weekly_diff\": null,\n \"weekly_diff_percent\": null,\n \"monthly_diff\": null,\n \"monthly_diff_percent\": null,\n \"timestp\": \"2022-02-06T00:00:00.000Z\",\n \"flags\": false,\n \"value\": 48414,\n \"diff\": 48414\n },\n {\n \"weekly_diff\": null,\n \"weekly_diff_percent\": null,\n \"monthly_diff\": null,\n \"monthly_diff_percent\": null,\n \"timestp\": \"2022-02-16T00:00:00.000Z\",\n \"flags\": false,\n \"value\": 35149,\n \"diff\": 35149\n }\n ]\n }\n}", - "type": "json" - }, - { - "title": "Success-Response (Selected Field 'likes' Usage):", - "content": " HTTP/1.1 200 OK\n {\n \"obj\": {\n \"likes\": [\n {\n \"weekly_diff\": null,\n \"weekly_diff_percent\": null,\n \"monthly_diff\": null,\n \"monthly_diff_percent\": null,\n \"value\": 462391,\n \"timestp\": \"2022-02-06T00:00:00.000Z\",\n \"flags\": false,\n \"diff\": null\n },\n {\n \"weekly_diff\": null,\n \"weekly_diff_percent\": null,\n \"monthly_diff\": null,\n \"monthly_diff_percent\": null,\n \"value\": 464773,\n \"timestp\": \"2022-02-16T00:00:00.000Z\",\n \"flags\": false,\n \"diff\": 2382\n }\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "All Fields usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/curator/spotify/324010/stat/facebook?since=2022-02-01&until=2022-03-01", - "type": "curl" - }, - { - "title": "Selected Field usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/curator/spotify/324010/stat/facebook?field=likes&since=2022-02-01&until=2022-03-01", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/curator.docs.ts", - "groupTitle": "Curator" - }, - { - "type": "get", - "url": "/api/curator/:platform/:id/urls", - "title": "Social / Streaming Service URLs", - "version": "1.0.0", - "name": "GetCuratorURLs", - "description": "

Get social media URLs belonging to this curator.

", - "group": "Curator", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric curator ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "applemusic", - "deezer" - ], - "optional": false, - "field": "platform", - "description": "" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "domain", - "description": "

Domain

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "url", - "description": "

Domain URL

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"domain\": \"youtube\",\n \"url\": \"https://www.youtube.com/channel/UC4sYm9wRMr4uUVln4DywKOg\"\n },\n {\n \"domain\": \"website\",\n \"url\": \"https://filtr.com/\"\n },\n {\n \"domain\": \"soundcloud\",\n \"url\": \"https://soundcloud.com/filtr\"\n },\n {\n \"domain\": \"facebook\",\n \"url\": \"https://www.facebook.com/filtr/\"\n },\n {\n \"domain\": \"instagram\",\n \"url\": \"https://www.instagram.com/filtr/\"\n },\n {\n \"domain\": \"tiktok\",\n \"url\": \"https://www.tiktok.com/@filtr.music\"\n },\n {\n \"domain\": \"spotify\",\n \"url\": \"https://open.spotify.com/user/myplay.com\"\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/curator/spotify/115196/urls", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/curator.docs.ts", - "groupTitle": "Curator" - }, - { - "type": "get", - "url": "/api/curator/:platform/lists", - "title": "Curator List", - "name": "GetPlaylistCuratorList", - "description": "

List curators by platform and other parameters.

Each platform has different sorting options due to the variety of data available. Valid combinations are provided in the below table for your convenience:

platform Valid sortColumn values
spotify num_playlists (default), followers, num_updates, num_tracks_updated, fdiff_month, fdiff_percent_month, sp_playlist_total_reach
applemusic, itunes num_playlists (default)
deezer num_playlists, total_reach (default)
youtube total_playlist_views (default), num_playlists, channel_views, channel_subscribers
amazon total_playlists (default)

Note that if no sortColumn value is passed the (default) option will be used.

", - "version": "1.0.0", - "group": "Curator", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "applemusic", - "deezer", - "itunes", - "youtube", - "amazon" - ], - "optional": false, - "field": "platform", - "description": "

Note: itunes and applemusic return the same data.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned. Max value: 100,000.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned. Max value: 100.

Note: offset + limit has to be lower than 10,000.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "sortColumn", - "description": "

Sort by column. See table above for allowed values.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "sortOrderDesc", - "defaultValue": "true", - "description": "

Whether to sort in descending order.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "editorial", - "description": "

When set to true it will select only official platform curators. Set to false, it will exclude these results. It is applicable only to Spotify, Apple Music, Amazon and YouTube.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "majorLabel", - "description": "

When set to true it will select only major label curators. Set to false, it will exclude these results. It is applicable only to Spotify and YouTube.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "brand", - "description": "

When set to true, it will select only curators associated to a brand. Set to false, it will exclude these results. It is applicable only to Spotify and YouTube.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "popularIndie", - "description": "

When set to true, it will select curators that are not defined as official editorial, brand, or major label and that have 100K+ reached followers. Set to false, it will exclude these results. It is applicable only to Spotify and YouTube.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "indie", - "description": "

When set to true, it will include results that are not major label, official editorial, brand and popular indie curators. Set to false, it will exclude these results. It is applicable only to Spotify, Apple Music and YouTube.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "audiobook", - "description": "

When set to true, it will select only audiobook curators. Set to false, it will exclude these results. It is applicable only to Spotify and YouTube.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "withSocialUrls", - "defaultValue": "false", - "description": "

Option to filter curators with social URLs data to response. The actual social URLs are currently not included in the response. It is applicable only to Spotify.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "code2", - "description": "

2-Letter Country Code. (Full List in Introduction). It is applicable only to Apple Music and YouTube.

" - }, - { - "group": "Parameter", - "type": "Number[]", - "optional": true, - "field": "shortlistIds", - "description": "

Array of shortlist IDs this item is included on

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "obj.position", - "description": "

Array of curator objects

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.user_id", - "description": "

Spotify curator ID

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.owner_id", - "description": "

Chartmetric curator ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.owner_name", - "description": "

Curator name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.url", - "description": "

Curator url

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.num_playlists", - "description": "

Count of playlist

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.sp_playlist_total_reach", - "description": "

Sum of followers from all Spotify playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.followers", - "description": "

Count of followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.fdiff_month", - "description": "

Difference in number of followers compared to last month, indicating monthly follower growth

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.fdiff_percent_month", - "description": "

Percent of difference of followers compared to last month

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "obj.tags", - "description": "

Array of tags

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.submithub_id", - "description": "

submithub_id
Note: This field is deprecated and it's hard-coded to null. It will be removed after 03-31-2024.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.num_updates", - "description": "

Number of updates

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "obj.num_tracks_updated", - "description": "

Number of tracks updated

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "offset", - "description": "

The page number

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "total", - "description": "

Total curators

" - } - ] - }, - "examples": [ - { - "title": "Success-Response (Spotify):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"position\": 1,\n \"user_id\": \"spotify\",\n \"owner_id\": 2,\n \"owner_name\": \"Spotify\",\n \"url\": \"https://i.scdn.co/image/ab6775700000ee8555c25988a6ac314394d3fbf5\",\n \"num_playlists\": 138039,\n \"sp_playlist_total_reach\": 2793372862,\n \"followers\": 11213310,\n \"fdiff_month\": 113368,\n \"fdiff_percent_month\": 0.013958,\n \"tags\": [\n \"pop\",\n \"hip-hop/rap\",\n \"rock\"\n ],\n \"submithub_id\": null,\n \"num_updates\": 0,\n \"num_tracks_updated\": 0,\n \"editorial\": true,\n \"major_label\": false,\n \"brand\": false,\n \"popular_indie\": false,\n \"indie\": false,\n \"audiobook\": true\n },\n {\n \"position\": 2,\n \"user_id\": \"gatoradeamplify\",\n \"owner_id\": 165022,\n \"owner_name\": \"Gatorade Amplify\",\n \"url\": \"https://i.scdn.co/image/ab6775700000ee85b1d5643e3b42fb61fe089aba\",\n \"num_playlists\": 14371,\n \"sp_playlist_total_reach\": 5590,\n \"followers\": 4032,\n \"fdiff_month\": -2,\n \"fdiff_percent_month\": -0.000358,\n \"tags\": [\n \"Rock\",\n \"Pop\",\n \"Hip-Hop/Rap\"\n ],\n \"submithub_id\": null,\n \"num_updates\": 0,\n \"num_tracks_updated\": 0,\n \"editorial\": false,\n \"major_label\": false,\n \"brand\": true,\n \"popular_indie\": false,\n \"indie\": false,\n \"audiobook\": false\n },\n ...\n ],\n \"offset\": 0,\n \"total\": 2712006\n}", - "type": "json" - }, - { - "title": "Success-Response (Deezer):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"position\": 1,\n \"total_reach\": 25973829,\n \"owner_id\": 637006841,\n \"url\": \"https://api.deezer.com/user/637006841/image\",\n \"owner_name\": \"Deezer Charts\",\n \"code2\": \"gb\",\n \"num_playlists\": 66,\n \"tags\": [\n \"'Hip-Hop/Rap'\",\n \"'Latin'\",\n \"'Pop'\"\n ]\n },\n {\n \"position\": 2,\n \"total_reach\": 21255983,\n \"owner_id\": 1990304482,\n \"url\": \"https://api.deezer.com/user/1990304482/image\",\n \"owner_name\": \"Deezer Artist Editor\",\n \"code2\": \"br\",\n \"num_playlists\": 1702,\n \"tags\": [\n \"'Hip-Hop/Rap'\",\n \"'Pop'\",\n \"'Rock'\"\n ]\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage (Spotify):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/curator/spotify/lists?indie=true&limit=5&offset=0&withSocialUrls=false", - "type": "curl" - }, - { - "title": "Example usage (Deezer):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/curator/deezer/lists?limit=5&offset=0", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/curator.docs.ts", - "groupTitle": "Curator" - }, - { - "type": "get", - "url": "/api/event/venue/:venueId", - "title": "Get Events for Venue", - "name": "Event_list_for_Venue", - "version": "1.0.0", - "group": "Event", - "description": "

Get a list of events for a venue.

", - "parameter": { - "fields": { - "Path Parameters": [ - { - "group": "Path Parameters", - "type": "Number", - "optional": false, - "field": "venueId", - "description": "

Venue ID.

" - } - ], - "Query Parameters": [ - { - "group": "Query Parameters", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "20", - "description": "

Limit of number of matched events. Max value: 100

" - }, - { - "group": "Query Parameters", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "obj", - "description": "" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "obj.venueInfo", - "description": "

Venue information.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.venueInfo.id", - "description": "

Venue ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.venueInfo.name", - "description": "

Name of the venue.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.venueInfo.image_uri", - "description": "

Image URI of the venue.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.venueInfo.street_address", - "description": "

Street address of the venue.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.venueInfo.lat", - "description": "

Latitude of the venue.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.venueInfo.lng", - "description": "

Longitude of the venue.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.venueInfo.zip", - "description": "

ZIP code of the venue.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.venueInfo.city_id", - "description": "

City ID of the venue.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.venueInfo.code2", - "description": "

Country code of the venue.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.venueInfo.capacity", - "description": "

Capacity of the venue.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.venueInfo.description", - "description": "

Description of the venue.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.venueInfo.phone", - "description": "

Phone number of the venue.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.venueInfo.parent", - "description": "

Parent venue information.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.venueInfo.created_at", - "description": "

Creation date of the venue.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.venueInfo.modified_at", - "description": "

Last modified date of the venue.

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "obj.events", - "description": "

List of events at the venue.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.events.event_id", - "description": "

Event ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.events.event_name", - "description": "

Name of the event.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.events.event_image_url", - "description": "

Image URL of the event.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.events.start_date", - "description": "

Start date of the event.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.events.end_date", - "description": "

End date of the event.

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "obj.events.artists", - "description": "

List of artists performing at the event.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.events.artists.id", - "description": "

Artist ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.events.artists.name", - "description": "

Name of the artist.

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "obj.events.artists.is_headliner", - "description": "

Whether the artist is a headliner.

" - } - ] - }, - "examples": [ - { - "title": "Example usage", - "content": "{\n \"obj\": {\n \"venueInfo\": {\n \"id\": 1,\n \"name\": \"Greek Theatre\",\n \"image_url\": \"https://www.jambase.com/wp-content/uploads/2015/06/greek-1480x832.jpg\",\n \"street_address\": \"2001 Gayley Rd\",\n \"lat\": 37.8726,\n \"lng\": -122.254,\n \"zip\": \"94720\",\n \"city_id\": 6312,\n \"code2\": \"US\",\n \"capacity\": 8500,\n \"description\": null,\n \"phone\": null,\n \"parent\": null,\n \"created_at\": \"2025-03-06T21:59:43.444Z\",\n \"modified_at\": \"2025-08-01T09:00:01.620Z\"\n },\n \"events\": [\n {\n \"event_id\": 8553348,\n \"event_name\": \"Modest Mouse at Greek Theatre\",\n \"event_image_url\": \"https://www.jambase.com/wp-content/uploads/2019/03/modest-mouse-2019-press-1480x832.jpg\",\n \"start_date\": \"2016-07-28T00:00:00.000Z\",\n \"end_date\": \"2016-07-28T00:00:00.000Z\",\n \"artists\": [\n {\n \"id\": 20,\n \"name\": \"Modest Mouse\",\n \"is_headliner\": null\n }\n ]\n },\n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage", - "content": "GET curl -H 'Authorization: Bearer ACCESS_KEY' https://api.chartmetric.com/api/event/venue/1?limit=5&offset=0", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/event.docs.ts", - "groupTitle": "Event" - }, - { - "type": "get", - "url": "/api/festival/list", - "title": "Festival List", - "version": "1.0.0", - "name": "GetFestivals", - "description": "

Get Chartmetric's Festival List.

The following sort columns are available for sorting the festival list:

sortColumn Description
festivalName Sort by festival name
rank Sort by festival popularity rank
startDate Sort by festival date (default)
numberOfDays Sort by the number of days the festival lasts
eventSize Sort by festival size category. Valid values are small, medium, large, and mega.
", - "group": "Festival", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String[]", - "optional": true, - "field": "code2s", - "description": "

List of country codes to filter festivals by country.

" - }, - { - "group": "Parameter", - "type": "Integer[]", - "optional": true, - "field": "genreIds", - "description": "

List of genre IDs to filter festivals by genre.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "sortColumn", - "defaultValue": "startDate", - "description": "

Column to sort the festivals by.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "sortOrderDesc", - "defaultValue": "false", - "description": "

Sort order. If true, sorts in descending order.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "nameSearch", - "description": "

Search term to filter festivals by name.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

Maximum number of festivals to return.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

Offset for pagination.

" - }, - { - "group": "Parameter", - "type": "String[]", - "optional": true, - "field": "eventSizes", - "description": "

List of festival size categories to filter by. Valid values are small, medium, large, and mega.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric event ID

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "name", - "description": "

Festival name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "date", - "description": "

Festival start date in 'YYYY-MM-DD HH:MM:SS.MMM' format

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "numberOfDays", - "description": "

Number of days the festival lasts

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "city", - "description": "

City where the festival is held

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "country", - "description": "

Country where the festival is held

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "performersCount", - "description": "

Number of performers at the festival

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "popularityRank", - "description": "

Popularity rank of the festival

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "eventSize", - "description": "

Size category of the festival (e.g., 'Mega (60K+)')

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "topGenres", - "description": "

List of top genres at the festival

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "topGenres.id", - "description": "

Genre ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "topGenres.name", - "description": "

Genre name

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "links", - "description": "

Links to external festival pages

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "links.jambase", - "description": "

URL to the festival on JamBase

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "links.songkick", - "description": "

URL to the festival on Songkick

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "links.seatgeek", - "description": "

URL to the festival on SeatGeek

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 642168,\n \"name\": \"Rock Werchter\",\n \"date\": \"2025-07-03 00:00:00.000\",\n \"numberOfDays\": 3,\n \"city\": \"Werchter\",\n \"country\": \"BE\",\n \"performersCount\": 89,\n \"popularityRank\": 21,\n \"eventSize\": \"Mega (60K+)\",\n \"topGenres\": [\n {\n \"id\": 501120,\n \"name\": \"pop\"\n },\n {\n \"id\": 501122,\n \"name\": \"rock\"\n },\n {\n \"id\": 501124,\n \"name\": \"electronic\"\n }\n ],\n \"links\": {\n \"jambase\": \"https://www.jambase.com/festival/rock-werchter-2025\",\n \"songkick\": null,\n \"seatgeek\": null\n }\n },\n {\n \"id\": 666842,\n \"name\": \"La Nuit de l\\u2019Erdre\",\n \"date\": \"2025-07-03 00:00:00.000\",\n \"numberOfDays\": 3,\n \"city\": \"Nort-sur-Erdre\",\n \"country\": \"FR\",\n \"performersCount\": 21,\n \"popularityRank\": 134,\n \"eventSize\": \"Mega (60K+)\",\n \"topGenres\": [\n {\n \"id\": 501120,\n \"name\": \"pop\"\n },\n {\n \"id\": 501149,\n \"name\": \"european\"\n },\n {\n \"id\": 501146,\n \"name\": \"dance\"\n }\n ],\n \"links\": {\n \"jambase\": \"https://www.jambase.com/festival/la-nuit-de-lerdre-2025\",\n \"songkick\": null,\n \"seatgeek\": null\n }\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer ACCESS_KEY' https://api.chartmetric.com/api/festival/list", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/festival.docs.ts", - "groupTitle": "Festival" - }, - { - "type": "get", - "url": "/api/genre", - "title": "Genre List", - "version": "1.0.0", - "name": "GetGenres", - "description": "

Get Chartmetric's Genre List.

", - "group": "Genre", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "isLegacy", - "defaultValue": "false", - "description": "

Flag to return legacy genres. If this is set to true, the response will only return legacy genres. Otherwise, it will return only new genres.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric genre ID

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "name", - "description": "

Genre name

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 501120,\n \"name\": \"pop\"\n },\n {\n \"id\": 501121,\n \"name\": \"hip-hop/rap\"\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer ACCESS_KEY' https://api.chartmetric.com/api/genre", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/genre.docs.ts", - "groupTitle": "Genre" - }, - { - "type": "get", - "url": "/api/playlist/by/:type/:id/evolution", - "title": "Playlist Evolution Stats (given Artist, Album or Track Chartmetric ID)", - "name": "GetEvolution", - "description": "

Please note that some tracks have only the latest stats and no evolution.

", - "version": "1.0.0", - "group": "Playlist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "type", - "description": "

Chartmetric metadata type (artist, track, album)

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric metadata ID

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "since", - "description": "

beginning date, in ISO date format, default is 6 months from current date (e.g. "2017-03-25")

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "until", - "description": "

end date, in ISO date format, default is current date (e.g. "2017-03-25")

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "spotify", - "description": "

Array of objects denoting stats for Spotify playlists for this artist/track/album

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "itunes", - "description": "

Array of objects denoting stats for iTunes playlists for this artist/track/album

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "deezer", - "description": "

Array of objects denoting stats for Deezer playlists for this artist/track/album

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "amazon", - "description": "

Array of objects denoting stats for Amazon playlists for this artist/track/album

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "youtube", - "description": "

Array of objects denoting stats for YouTube playlists for this artist/track/album

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "soundcloud", - "description": "

Array of objects denoting stats for SoundCloud playlists for this artist/track/album

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "created_at", - "description": "

Date for which the stats are recorded

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "playlist_count", - "description": "

Total number of playlists for the date

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "ed_playlist_count", - "description": "

Total number of editorial playlists for the date

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "playlist_total_reach", - "description": "

Total number of followers/fans/subscribers for the playlists for the date

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "ed_playlist_total_reach", - "description": "

Total number of followers/fans/subscribers for the editorial playlists for the date

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"spotify\": [\n {\n \"created_at\": \"2018-12-01\",\n \"playlist_count\": 1095,\n \"ed_playlist_count\": 8,\n \"playlist_total_reach\": 4296113,\n \"ed_playlist_total_reach\": 3383510\n },\n {\n \"created_at\": \"2018-12-02\",\n \"playlist_count\": 1097,\n \"ed_playlist_count\": 10,\n \"playlist_total_reach\": 4296134,\n \"ed_playlist_total_reach\": 3383557,\n },\n ...\n ],\n \"amazon\": [],\n \"deezer\": [],\n \"youtube\": [],\n \"itunes\": [\n {\n \"created_at\": \"2018-12-01\",\n \"playlist_count\": 15,\n \"ed_playlist_count\": 3,\n \"playlist_total_reach\": null,\n \"ed_playlist_total_reach\": null\n },\n {\n \"created_at\": \"2018-12-01\",\n \"playlist_count\": 15,\n \"ed_playlist_count\": 3,\n \"playlist_total_reach\": null,\n \"ed_playlist_total_reach\": null\n },\n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage (artist):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/playlist/by/artist/5408/evolution", - "type": "curl" - }, - { - "title": "Example usage (track):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/playlist/by/track/73505038/evolution", - "type": "curl" - }, - { - "title": "Example usage (album):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/playlist/by/album/18952090/evolution", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/playlist.docs.ts", - "groupTitle": "Playlist" - }, - { - "type": "get", - "url": "/api/playlist/by/:type/:id/playlist-evolution", - "title": "Playlist Evolution Stats (given Artist, Album or Track Chartmetric ID)", - "name": "GetEvolution", - "description": "

Please note that this API is deprecated and will be removed soon. Please use /api/playlist/by/:type/:id/evolution instead.

", - "version": "1.0.0", - "group": "Playlist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "type", - "description": "

Chartmetric metadata type (artist, track, album)

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": false, - "field": "id", - "description": "

Chartmetric metadata ID

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "since", - "description": "

beginning date, in ISO date format (e.g. "2017-03-25")

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "until", - "description": "

end date, in ISO date format (e.g. "2017-03-25")

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "spotify_playlist_count", - "description": "

Number of spotify playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "itunes_playlist_count", - "description": "

Number of apple music playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "deezer_playlist_count", - "description": "

Number of deezer playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "amazon_playlist_count", - "description": "

Number of amazon playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "youtube_playlist_count", - "description": "

Number of youtube playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "spotify_ed_playlist_count", - "description": "

Number of spotify editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "itunes_ed_playlist_count", - "description": "

Number of apple music editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "deezer_ed_playlist_count", - "description": "

Number of deezer editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "amazon_ed_playlist_count", - "description": "

Number of amazon editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "youtube_ed_playlist_count", - "description": "

Number of youtube editorial playlists featuring this artist

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "spotify_playlist_total_reach", - "description": "

Sum of followers from all spotify playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "spotify_ed_playlist_total_reach", - "description": "

Sum of followers from spotify editorial playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "deezer_playlist_total_reach", - "description": "

Sum of fans from all deezer playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "deezer_ed_playlist_total_reach", - "description": "

Sum of fans from deezer editorial playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "youtube_playlist_total_reach", - "description": "

Sum of views from all youtube playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "youtube_ed_playlist_total_reach", - "description": "

Sum of views from youtube editorial playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "created_at", - "description": "

Date for which the stats are recorded

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"cm_artist\": 5408,\n \"spotify_playlist_total_reach\": \"62406265\",\n \"spotify_playlist_count\": 4510,\n \"spotify_ed_playlist_total_reach\": \"39512263\",\n \"spotify_ed_playlist_count\": 76,\n \"itunes_playlist_count\": 185,\n \"itunes_ed_playlist_count\": 42,\n \"amazon_playlist_count\": 267,\n \"amazon_ed_playlist_count\": 234,\n \"deezer_playlist_count\": 80,\n \"deezer_playlist_total_reach\": \"10389434\",\n \"deezer_ed_playlist_count\": 8,\n \"deezer_ed_playlist_total_reach\": \"619750\",\n \"youtube_playlist_count\": 12,\n \"youtube_playlist_total_reach\": \"33750705\",\n \"youtube_ed_playlist_count\": 9,\n \"youtube_ed_playlist_total_reach\": \"28382560\",\n \"created_at\": \"2019-11-01T07:00:00.000Z\"\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/playlist/by/artist/5408/playlist-evolution?since=2019-11-01&until=2019-12-01", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/playlist.docs.ts", - "groupTitle": "Playlist" - }, - { - "type": "get", - "url": "/api/playlist/:platform/:id/journey-progression/:type", - "title": "Playlist Journey or Progression", - "name": "GetPlaylistJourneyProgress", - "version": "1.0.0", - "group": "Playlist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "applemusic", - "deezer", - "soundcloud" - ], - "optional": false, - "field": "platform", - "description": "

Streaming platform type

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "journey", - "progression" - ], - "optional": false, - "field": "type", - "description": "

Look Back or Look Ahead

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "overlap", - "followers" - ], - "optional": true, - "field": "sortColumn", - "defaultValue": "overlap", - "description": "

Column to sort the results by

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "sortOrderDesc", - "defaultValue": "true", - "description": "

Sort Order

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

Limit of number of matched playlists. Max 50.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "total", - "description": "

Total number of matched playlists

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

List of results

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.plid", - "description": "

Matched playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

Matched playlist name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.total_percent_overlap", - "description": "

Total overlap percent for the target playlist and matched playlist combination

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.image_url", - "description": "

Image link for the matched playlist ID

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.count_distinct_tracks", - "description": "

Number of distinct tracks for the target playlist and matched playlist combination

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "data.events", - "description": "

Details for target playlist and matched playlist combination per day

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.events.days", - "description": "

Number of days

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "data.events.day_percent_overlap", - "description": "

Overlap percent for the target playlist and matched playlist combination

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.events.tracks_added", - "description": "

Track info

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.events.tracks_added.id", - "description": "

Track id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.events.tracks_added.name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.events.tracks_added.image_url", - "description": "

Image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.events.tracks_added.isrc", - "description": "

Track isrc

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"total\": \"24\",\n \"data\": [\n {\n \"plid\": \"1280262301\",\n \"name\": \"\",\n \"followers\": 1022,\n \"image_url\": null,\n \"total_percent_overlap\": 0.055556,\n \"count_distinct_tracks\": \"2\",\n \"events\": [\n {\n \"days\": 16,\n \"day_percent_overlap\": 0.055556,\n \"tracks_added\": [\n {\n \"id\": 20654220,\n \"name\": \"Flou\",\n \"image_url\": \"https://is3-ssl.mzstatic.com/image/thumb/Music118/v4/4f/a7/07/4fa707be-69f1-e04a-cf70-164918eb8fb2/source/160x160bb.jpeg\",\n \"isrc\": \"BE8HB1800014\"\n },\n {\n \"id\": 20283133,\n \"name\": \"Le temps est bon\",\n \"image_url\": \"https://i.scdn.co/image/57ef31f293dc758e4ba815512670ad761f082643\",\n \"isrc\": \"FRZ051800423\"\n }\n ]\n }\n ]\n },\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage (Deezer):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/playlist/deezer/1189520191/journey-progression/journey", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/playlist.docs.ts", - "groupTitle": "Playlist" - }, - { - "type": "get", - "url": "/api/playlist/:platform/:id/updated", - "title": "Playlist Last Updated Time", - "name": "GetPlaylistLastUpdated", - "version": "1.0.0", - "group": "Playlist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "itunes", - "applemusic", - "deezer", - "amazon", - "youtube", - "boomplay", - "soundcloud" - ], - "optional": false, - "field": "platform", - "description": "

Streaming platform type

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "id", - "description": "

Chartmetric playlist ID or platform playlist ID. The id data type depends on the platform. Spotify, iTunes/Apple Music, and Boomplay use numerical ids. Deezer uses bigInt id, so it takes in a string of numbers. Amazon and YouTube use string ids.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "us", - "ca", - "br", - "de", - "gb", - "..." - ], - "optional": true, - "field": "storefront", - "description": "

Storefront of the playlist

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "sys_last_updated", - "description": "

Last updated time of a playlist and its stats

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "playlist_last_updated", - "description": "

Last date when a track was added to or removed from the playlist

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "last_added", - "description": "

Last date when a track was added to the playlist

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "last_removed", - "description": "

Last date when a track was removed from the playlist

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"sys_last_updated\": \"2022-07-28T00:00:00.000Z\",\n \"last_added\": \"2022-07-22T00:04:38.539Z\",\n \"last_removed\": \"2022-07-22T00:04:41.687Z\"\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/playlist/deezer/1189520191/updated", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/playlist.docs.ts", - "groupTitle": "Playlist" - }, - { - "type": "get", - "url": "/api/playlist/:platform/lists", - "title": "Playlist List", - "name": "GetPlaylistList", - "version": "1.0.0", - "group": "Playlist", - "description": "

sortColumn options for each platform

Each platform supports different sortColumns. Valid sortColumns are provided in the below table for your convenience.

Default values are indicated by ✅

sortColumn Amazon iTunes Deezer Spotify YouTube SoundCloud
active_ratio ✔️ ✔️ ✔️ ✔️ ✔️
last_updated ✔️ ✔️ ✔️ ✔️ ✔️ ✔️
num_track ✔️ ✔️ ✔️ ✔️ ✔️ ✔️
rank
fdiff_percent_week ✔️ ✔️ ✔️
fdiff_week ✔️ ✔️ ✔️
fdiff_month ✔️ ✔️ ✔️
fdiff_percent_month ✔️ ✔️ ✔️
followers
likes
playlist_updated ✔️
views
vdiff_week ✔️
vdiff_percent_week ✔️
vdiff_month ✔️
vdiff_percent_month ✔️
", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "itunes", - "deezer", - "amazon", - "youtube", - "soundcloud" - ], - "optional": false, - "field": "platform", - "description": "

Streaming platform type.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "sortColumn", - "description": "

Column to sort by. Each platform support different sortColumns. Valid sortColumns are provided in the table above for your convenience.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "sortOrderDesc", - "defaultValue": "true", - "description": "

Whether to sort in descending order.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "code2", - "description": "

Country code.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "tagIds", - "description": "

Playlist tagIds.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "curatorIds", - "description": "

Spotify's user IDs.

" - }, - { - "group": "Parameter", - "type": "Number[]", - "optional": true, - "field": "shortlistIds", - "description": "

Array of shortlist IDs to filter by.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned. Maximum 100.

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned. Max value: 100,000.

Note: Due to data manipulation limitations, offset + limit has to be lower than 10,000 for both spotify and itunes.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "indie", - "description": "

If the playlist was curated by indie. Set to false to exclude these results. Applies only to platforms Spotify, Apple Music (iTunes), and Deezer platforms.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "majorCurator", - "description": "

If the playlist was curated by major labels. Set to false to exclude these results. Applies only to Spotify, and Deezer platforms.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "editorial", - "description": "

If the playlist was curated by editorials. Set to false to exclude these results. Applies only to Spotify, Apple Music (itunes), and Deezer platforms.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "newMusicFriday", - "description": "

A curated selection of Spotify's most anticipated new tracks of the week. Set to false to exclude these results. Applies only to Spotify platform.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "position", - "description": "

Absolute position of playlist in results after filtering and sorting

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "code2", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "last_updated", - "description": "

Playlist last update date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "owner_name", - "description": "

Playlist owner (user) name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "owner_id", - "description": "

Chartmetric curator ID

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "user_id", - "description": "

Playlist owner (user) ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist_id", - "description": "

Spotify playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Playlist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Playlist image

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "personalized", - "description": "

If playlist is personalized

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "editorial", - "description": "

If this playlist is curated by an official editorial team. Applies only to Spotify, Apple Music, and Deezer platforms.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "followers", - "description": "

Followers count of playlist

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "likes", - "description": "

Likes count of playlist (SoundCloud)lowers/likes compared to previous week, indicating weekly follower/like growth

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "fdiff_week", - "description": "

Difference in number of followers/likes compared to previous week, indicating weekly follower growth

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "fdiff_percent_week", - "description": "

Weekly follower/like growth percentage

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "fdiff_month", - "description": "

Difference in number of followers/likes compared to previous month, indicating monthly follower/like growth

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "fdiff_percent_month", - "description": "

Monthly follower/like growth perfentage

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "catalog", - "description": "

Catalog

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "active_ratio", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Genre

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tags", - "description": "

Tags

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "offset", - "description": "

The offset of entries to be returned

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "total", - "description": "

Total playlists

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"position\": 1,\n \"id\": 1645080,\n \"code2\": null,\n \"playlist_id\": \"37i9dQZF1DXcBWIGoYBM5M\",\n \"name\": \"Today's Top Hits\",\n \"image_url\": \"https://i.scdn.co/image/ab67706f00000003a3b1c73cabef96367d1fc553\",\n \"personalized\": false,\n \"followers\": 27093574,\n \"last_updated\": \"2020-12-21T06:55:23.752Z\",\n \"owner_name\": \"Spotify\",\n \"owner_id\": 2,\n \"user_id\": \"spotify\",\n \"editorial\": true,\n \"fdiff_week\": 15895,\n \"fdiff_percent_week\": 0.05866704777,\n \"fdiff_month\": 56338,\n \"fdiff_percent_month\": 0.2079386057,\n \"catalog\": \"frontline \",\n \"active_ratio\": 1,\n \"genre\": \"pop, top lists\"\n }, ...\n ],\n \"offset\": 0,\n \"total\": 1436332\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/playlist/spotify/lists?limit=5&offset=0&sortColumn=followers", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/playlist.docs.ts", - "groupTitle": "Playlist" - }, - { - "type": "get", - "url": "/api/playlist/:platform/:id", - "title": "Metadata", - "name": "GetPlaylistMetadata", - "version": "1.0.0", - "group": "Playlist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "applemusic", - "deezer", - "amazon", - "youtube", - "soundcloud" - ], - "optional": false, - "field": "platform", - "description": "

Streaming platform type

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "ae", - "ag", - "ai", - "am", - "ar", - "at", - "au", - "az", - "bb", - "be", - "bg", - "bh", - "bm", - "bo", - "br", - "bw", - "by", - "bz", - "ca", - "ch", - "cl", - "cn", - "co", - "cr", - "cv", - "cy", - "cz", - "de", - "dk", - "dm", - "do", - "ec", - "ee", - "eg", - "es", - "fi", - "fj", - "fm", - "fr", - "gb", - "gd", - "gh", - "gm", - "gr", - "gt", - "gw", - "hk", - "hn", - "hu", - "id", - "ie", - "il", - "in", - "it", - "jo", - "jp", - "ke", - "kg", - "kh", - "kn", - "kr", - "ky", - "kz", - "la", - "lb", - "lk", - "lt", - "lu", - "lv", - "md", - "mn", - "mo", - "mt", - "mu", - "mx", - "my", - "ne", - "ng", - "ni", - "nl", - "no", - "np", - "nz", - "om", - "pa", - "pe", - "pg", - "ph", - "pl", - "pt", - "py", - "ro", - "ru", - "sa", - "se", - "sg", - "si", - "sk", - "sv", - "sz", - "th", - "tj", - "tm", - "tr", - "tt", - "tw", - "ua", - "ug", - "us", - "uz", - "ve", - "vg", - "vn", - "za", - "zw" - ], - "optional": true, - "field": "storefront", - "description": "

Apple Music storefront. Required for Apple Music playlists

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Playlist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist_id", - "description": "

Spotify playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "description", - "description": "

Playlist description

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Playlist Image

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "sys_last_updated", - "description": "

Chartmetric last updated

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "last_updated", - "description": "

Playlist last updated

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "personalized", - "description": "

Personalized playlist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "code2", - "description": "

Playlist Country

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "owner_name", - "description": "

Playlist owner (user) name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "owner_id", - "description": "

Chartmetric curator ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "user_id", - "description": "

Playlist owner (user) ID

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "editorial", - "description": "

If this playlist is curated by an official editorial team. Applies only to Spotify, Apple Music, and Deezer platforms.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "tags", - "description": "

tags

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "tags.id", - "description": "

tags id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tags.name", - "description": "

tags name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "followers", - "description": "

Followers count of playlist

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "likes", - "description": "

Likes count of playlist (SoundCloud)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "num_track", - "description": "

Number of tracks

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "fdiff_week", - "description": "

Difference in number of followers/likes compared to previous week, indicating weekly follower/like growth

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "fdiff_month", - "description": "

Difference in number of followers/likes compared to previous month, indicating monthly follower/like growth

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "catalog", - "description": "

catalog

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "active_ratio", - "description": "

Active ratio

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "new_sp_id", - "description": "

New Spotify playlist id (if transferred)

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n\n{\n \"obj\": {\n \"id\": 179228,\n \"name\": \"Disney Hits\",\n \"playlist_id\": \"5NtjgKz4doejP5HJtKXFcS\",\n \"description\": \"Your favorite Disney songs including Disney and Pixar’s Soul soundtrack featuring all-new music from Trent Reznor & Atticus Ross and Jon Batiste. Joe (Jamie Foxx) teams up with soul 22 (Tina Fey) and together they find the answers to some of life's biggest questions.\",\n \"image_url\": \"https://i.scdn.co/image/ab67706c0000bebb5eb3284524c46bb313c1901f\",\n \"sys_last_updated\": \"2020-12-20T02:54:08.370Z\",\n \"last_updated\": \"2020-12-19T03:57:31.173Z\",\n \"personalized\": false,\n \"code2\": null,\n \"owner_name\": \"Walt Disney Records\",\n \"owner_id\": 40857,\n \"user_id\": \"128899670\",\n \"editorial\": false,\n \"tags\": [\n {\n \"id\": 12463,\n \"name\": \"hollywood\"\n },\n {\n \"id\": 25588,\n \"name\": \"movie tunes\"\n },\n {\n \"id\": 408416,\n \"name\": \"pop\"\n }\n ],\n \"followers\": 11334,\n \"num_track\": \"88\",\n \"fdiff_week\": 68,\n \"fdiff_month\": 425,\n \"catalog\": \"catalogue \",\n \"active_ratio\": 0.034091\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/playlist/spotify/179228/", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/playlist.docs.ts", - "groupTitle": "Playlist" - }, - { - "type": "get", - "url": "/api/playlist/:platform/:id/snapshot", - "title": "Playlist Snapshot", - "name": "GetPlaylistSnapshot", - "version": "1.0.0", - "group": "Playlist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "applemusic", - "deezer", - "amazon", - "soundcloud" - ], - "optional": false, - "field": "platform", - "description": "

Streaming platform type

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "date", - "description": "

Datetime

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "us", - "ca", - "br", - "de", - "gb", - "..." - ], - "optional": true, - "field": "storefront", - "description": "

Apple Music storefront. Required for Apple Music playlists

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track_id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "added_at", - "description": "

When this track was added to the playlist

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "removed_at", - "description": "

When we detected this track as no longer being on the playlist

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "position", - "description": "

Track's position in the playlist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isrc", - "description": "

Track isrc

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "spotify_track_id", - "description": "

Spotify track id in Spotify

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "spotify_album_id", - "description": "

Spotify album id in Spotify

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "spotify_popularity", - "description": "

Spotify popularity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_track", - "description": "

Chartmetric track id

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_artist_names", - "description": "

Spotify artist names

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track_genre", - "description": "

Track genre

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "cm_artist", - "description": "

Chartmetric artist id

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist_names", - "description": "

Artist names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "code2s", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist_images", - "description": "

Artist images

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist_covers", - "description": "

Artist covers

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_artist_ids", - "description": "

Spotify artist ids in Spotify

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_track_ids", - "description": "

Spotify track ids in Spotify

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_album_ids", - "description": "

Spotify album ids in Spotify

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "spotify_duration_ms", - "description": "

Spotify duration ms

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_ids", - "description": "

Album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_names", - "description": "

Album names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_upc", - "description": "

Album upc

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "release_dates", - "description": "

Release dates

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"track_id\": 16979872,\n \"id\": 16979872,\n \"added_at\": \"2020-01-22T00:00:00.000Z\",\n \"removed_at\": null,\n \"position\": 1,\n \"name\": \"Into the Unknown\",\n \"isrc\": \"USWD11994667\",\n \"spotify_track_id\": \"3Z0oQ8r78OUaHvGPiDBR3W\",\n \"spotify_album_id\": \"4M07HWIlZr7zoXoxDHR5mz\",\n \"image_url\": \"https://i.scdn.co/image/43c6b410f1bc66fac51d61f36e3149b29b3f25f1\",\n \"spotify_popularity\": 79,\n \"cm_track\": 27031598,\n \"spotify_artist_names\": [\n \"Idina Menzel\",\n \"AURORA\"\n ],\n \"track_genre\": \"broadway,hollywood,movie tunes,show tunes,norwegian pop,pop\",\n \"cm_artist\": [\n 1805,\n 5172\n ],\n \"artist_names\": [\n \"Idina Menzel\",\n \"AURORA\"\n ],\n \"code2s\": [\n \"US\",\n \"NO\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/e303c5a39e4da4f7c40f1b6ccc1209795c9199d0\",\n \"https://i.scdn.co/image/a3ade0a92bb3c99514fb8d017024a7d9df1572b1\"\n ],\n \"artist_covers\": [\n null,\n null\n ],\n \"spotify_artist_ids\": [\n \"73Np75Wv2tju61Eo9Zw4IR\",\n \"1WgXqy2Dd70QQOU7Ay074N\"\n ],\n \"spotify_track_ids\": [\n \"3Z0oQ8r78OUaHvGPiDBR3W\"\n ],\n \"spotify_album_ids\": [\n \"4M07HWIlZr7zoXoxDHR5mz\"\n ],\n \"spotify_duration_ms\": 194848,\n \"album_ids\": [\n 5357061\n ],\n \"album_names\": [\n \"Frozen 2 (Original Motion Picture Soundtrack/Deluxe Edition)\"\n ],\n \"album_upc\": [\n \"050087433390\"\n ],\n \"album_label\": [\n \"Walt Disney Records\"\n ],\n \"release_dates\": [\n \"2019-11-15T00:00:00.000Z\"\n ]\n }, ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/playlist/spotify/179228/snapshot?date=2018-10-01", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/playlist.docs.ts", - "groupTitle": "Playlist" - }, - { - "type": "get", - "url": "/api/playlist/:platform/:id/stats", - "title": "Playlist Stats Over Time", - "name": "GetPlaylistStats", - "version": "1.0.0", - "description": "

Get spotify playlist followers/deezer playlist fans/youtube playlist views/soundcloud playlist likes. To lookup a Chartmetric id from a platform id, use our Chartmetric Search Engine endpoint to search the platform id with the appropriate type parameter set.

", - "group": "Playlist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "id", - "description": "

Chartmetric playlist ID (not a platform id)

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "deezer", - "youtube", - "soundcloud" - ], - "optional": false, - "field": "platform", - "description": "

Streaming platform type

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "since", - "description": "

start of the date range, in ISO date format (e.g. "2017-03-25")

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

end of the date range, in ISO date format (e.g. "2017-03-25")

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "value", - "description": "

desc

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "timestp", - "description": "

Time stamp

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "daily_diff", - "description": "

The difference between current and previous statistic

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "interpolated", - "description": "

interpolated

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 93878425,\n \"value\": 6527755,\n \"timestp\": \"Thu Oct 10 2019\",\n \"daily_diff\": 0,\n \"interpolated\": false\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage (Spotify):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/playlist/spotify/179228/stats?since=2020-01-01&until=2020-01-10", - "type": "curl" - }, - { - "title": "Example usage (Deezer):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/playlist/deezer/53362031/stats", - "type": "curl" - }, - { - "title": "Example usage (SoundCloud):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/playlist/soundcloud/228667052/stats", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/playlist.docs.ts", - "groupTitle": "Playlist" - }, - { - "type": "get", - "url": "/api/playlist/:platform/:id/:span/tracks", - "title": "Tracks (Current, Past)", - "name": "GetTracksByPlaylist", - "version": "1.0.0", - "description": "

Get playlist tracks

Applying track cluster types filters

You can refer to the Artist - Tracks section for usage examples.

", - "group": "Playlist", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "itunes", - "deezer", - "amazon", - "youtube", - "soundcloud" - ], - "optional": false, - "field": "platform", - "description": "

Streaming platform type

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "current", - "past" - ], - "optional": false, - "field": "span", - "description": "

Time span

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "us", - "ca", - "br", - "de", - "gb", - "..." - ], - "optional": true, - "field": "storefront", - "description": "

Storefront. Required for Itunes and Amazon playlists

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

Limit of number of matched tracks

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "withDetails", - "defaultValue": "true", - "description": "

Whether the response includes Artists, Album, and audio features informations.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "since", - "description": "

Only return tracks with removed_at after this date. Only applies to span = past

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "until", - "description": "

Only return tracks with added_at before this date. Only applies to span = past

" - }, - { - "group": "Parameter", - "type": "String[]", - "optional": true, - "field": "version_only", - "description": "

Types of version: Filter tracks where only the specified types are true and others false.
Can't be used with version_and, version_or, and version_not.

" - }, - { - "group": "Parameter", - "type": "String[]", - "optional": true, - "field": "version_and", - "description": "

Types of version: Filter tracks that contain all of the specified types.
Can’t be used with version_or.

" - }, - { - "group": "Parameter", - "type": "String[]", - "optional": true, - "field": "version_or", - "description": "

Types of version: Filter tracks that contain at least one of the specified types.
Can’t be used with version_and.

" - }, - { - "group": "Parameter", - "type": "String[]", - "optional": true, - "field": "version_not", - "description": "

Types of version: Filter tracks that do not contain any of the specified types.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_track_id", - "description": "

Chartmetric track ID

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track_id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isrc", - "description": "

Track isrc

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "added_at", - "description": "

Playlist add date

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "position", - "description": "

Position in playlist

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "period", - "description": "

Number of days the track has been in the playlist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track_name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "totaltracks", - "description": "

Number of total tracks in the playlist

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "cluster_types", - "description": "

Track cluster types

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "spotify_album_id", - "description": "

Spotify album id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "spotify_popularity", - "description": "

Spotify popularity score

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_track", - "description": "

Chartmetric track ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_artist_names", - "description": "

Spotify artist names

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track_genre", - "description": "

Track genre

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "cm_artist", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist_names", - "description": "

Artist's names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "code2s", - "description": "

Country codes

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist_images", - "description": "

Artist images

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist_covers", - "description": "

Artist covers

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_artist_ids", - "description": "

Spotify artist ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_track_ids", - "description": "

Spotify track ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_album_ids", - "description": "

Spotify album ids

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "spotify_duration_ms", - "description": "

Spotify duration

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_ids", - "description": "

album IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_names", - "description": "

Album names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_upc", - "description": "

Album upc

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album_label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "release_dates", - "description": "

Release dates

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audio_features", - "description": "

Audio features

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "audio_features.key", - "description": "

key

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "audio_features.mode", - "description": "

key

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audio_features.danceability", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audio_features.valence", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audio_features.energy", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audio_features.speechiness", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audio_features.acousticness", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audio_features.instrumentalness", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audio_features.liveness", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audio_features.tempo", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "audio_features.loudness", - "description": "

loudness

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "position_stats", - "description": "

Position stats of the track

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n {\n \"obj\": [\n {\n \"track_id\": 16979872,\n \"isrc\": \"USWD11994667\",\n \"added_at\": \"2020-01-22T00:00:00.000Z\",\n \"position\": 1,\n \"period\": 334,\n \"id\": 16979872,\n \"name\": \"Into the Unknown\",\n \"spotify_album_id\": \"4M07HWIlZr7zoXoxDHR5mz\",\n \"image_url\": \"https://i.scdn.co/image/43c6b410f1bc66fac51d61f36e3149b29b3f25f1\",\n \"spotify_popularity\": 79,\n \"cm_track\": 27031598,\n \"spotify_artist_names\": [\n \"Idina Menzel\",\n \"AURORA\"\n ],\n \"track_genre\": \"broadway,hollywood,movie tunes,show tunes,norwegian pop,pop\",\n \"cm_artist\": [\n 1805,\n 5172\n ],\n \"artist_names\": [\n \"Idina Menzel\",\n \"AURORA\"\n ],\n \"code2s\": [\n \"US\",\n \"NO\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/e303c5a39e4da4f7c40f1b6ccc1209795c9199d0\",\n \"https://i.scdn.co/image/a3ade0a92bb3c99514fb8d017024a7d9df1572b1\"\n ],\n \"artist_covers\": [\n null,\n null\n ],\n \"spotify_artist_ids\": [\n \"73Np75Wv2tju61Eo9Zw4IR\",\n \"1WgXqy2Dd70QQOU7Ay074N\"\n ],\n \"spotify_track_ids\": [\n \"3Z0oQ8r78OUaHvGPiDBR3W\"\n ],\n \"spotify_album_ids\": [\n \"4M07HWIlZr7zoXoxDHR5mz\"\n ],\n \"spotify_duration_ms\": 194848,\n \"album_ids\": [\n 5357061\n ],\n \"album_names\": [\n \"Frozen 2 (Original Motion Picture Soundtrack/Deluxe Edition)\"\n ],\n \"album_upc\": [\n \"050087433390\"\n ],\n \"album_label\": [\n \"Walt Disney Records\"\n ],\n \"release_dates\": [\n \"2019-11-15T00:00:00.000Z\"\n ],\n \"audio_features\": {\n \"key\": 10,\n \"mode\": 0,\n \"danceability\": \"0.323\",\n \"energy\": \"0.405\",\n \"speechiness\": \"0.0413\",\n \"acousticness\": \"0.849\",\n \"instrumentalness\": \"0.000544\",\n \"liveness\": \"0.138\",\n \"valence\": \"0.274\",\n \"tempo\": \"145.104\",\n \"loudness\": \"-8.533\"\n },\n \"position_stats\": []\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/playlist/spotify/179228/current/tracks", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/playlist.docs.ts", - "groupTitle": "Playlist" - }, - { - "type": "get", - "url": "/api/radio/:type/:id/airplay-totals", - "title": "Total Airplays", - "version": "1.1.0", - "name": "GetRadioAirplayTotals", - "description": "

This route returns the total # of times this 'album', 'track' or 'artist' has played on the radio across all markets Charmetric follows

", - "deprecated": { - "content": "in favor of Radio - Total Airplays by Entity. This endpoint will remain for backwards compatability for a period of time, but will be removed at some point in the future. Please migrate your usage to the newer endpoint." - }, - "group": "Radio", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "artist", - "album", - "track" - ], - "optional": false, - "field": "type", - "description": "" - }, - { - "group": "Parameter", - "type": "Number", - "optional": false, - "field": "id", - "description": "

Chartmetric id for the specified :type above

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": false, - "field": "station", - "description": "

Chartmetric Radio Station ID

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "since", - "description": "

Start date in ISO date format (e.g. "2017-03-25"). This parameter is required.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned. Maximum 100.

" - } - ] - } - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/radio/artist/236/airplay-totals?limit=3&since=2023-03-01", - "type": "curl" - } - ], - "success": { - "fields": { - "Response": [ - { - "group": "Response", - "type": "Object[]", - "optional": false, - "field": "countries", - "description": "

An array of country items, where each country item itself is a 2-item array where the first element is an object containing country metadata info, and the second element is an array of air dates with a count of plays for each date

" - }, - { - "group": "Response", - "type": "Object", - "optional": false, - "field": "countries.0", - "description": "

Not actually an object property name, "0" represents the 0-indexed element of each country item, and is an object with country metadata info

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "countries.0.code2", - "description": "

2-letter country code (countries.[i][0].code2)

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "countries.0.name", - "description": "

Name of the country (countries.[i][0].name)

" - }, - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "countries.0.plays", - "description": "

Total plays for the country across all dates (countries.[i][0].plays)

" - }, - { - "group": "Response", - "type": "Object[]", - "optional": false, - "field": "countries.1", - "description": "

Not actually an object property name, "1" represents the 1-indexed element of each country item, and is an array of air dates with play counts for each day

" - }, - { - "group": "Response", - "type": "Date", - "optional": false, - "field": "countries.1.air_date", - "description": "

date in ISO Date Format (e.g. "2020-01-11T08:00:00.000Z") (countries.[i][1].air_date)

" - }, - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "countries.1.count", - "description": "

Count of plays on this air_date

" - }, - { - "group": "Response", - "type": "Object[]", - "optional": false, - "field": "cities", - "description": "

An array of city items, where each city item itself is a 2-item array where the first element is an object containing city metadata info, and the second element is an array of air dates with a count of plays for each date

" - }, - { - "group": "Response", - "type": "Object", - "optional": false, - "field": "cities.0", - "description": "

Not actually an object property name, "0" represents the 0-indexed element of each city item, and is an object with city metadata info

" - }, - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "cities.0.id", - "description": "

Chartmetric city id

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "cities.0.name", - "description": "

Name of the city

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "cities.0.code2", - "description": "

2-letter country code

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "cities.0.country", - "description": "

Name of the country

" - }, - { - "group": "Response", - "type": "Float", - "optional": false, - "field": "cities.0.lat", - "description": "

Latitude

" - }, - { - "group": "Response", - "type": "Flat", - "optional": false, - "field": "cities.0.lng", - "description": "

Longitude

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "cities.0.plays", - "description": "

Total plays for the city across all dates

" - }, - { - "group": "Response", - "type": "Object[]", - "optional": false, - "field": "cities.1", - "description": "

Not actually an object property name, "1" represents the 1-indexed element of each city item, and is an array of air dates with play counts for each day

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "cities.1.air_date", - "description": "

date in ISO Date Format (e.g. "2020-01-11T08:00:00.000Z")

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "cities.1.count", - "description": "

Count of plays on this air_date

" - }, - { - "group": "Response", - "type": "Object[]", - "optional": false, - "field": "stations", - "description": "

An array of radio station items, where each radio station item itself is a 2-item array where the first element is an object containing radio station metadata info, and the second element is an array of air dates with a count of plays for each date

" - }, - { - "group": "Response", - "type": "Object", - "optional": false, - "field": "stations.0", - "description": "

Not actually an object property name, "0" represents the 0-indexed element of each station item, and is an object with station metadata info

" - }, - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "stations.0.id", - "description": "

Chartmetric radio station id

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "stations.0.name", - "description": "

Name of the radio station

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "stations.0.code2", - "description": "

2-letter country code

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "stations.0.city", - "description": "

Name of the city

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "stations.0.country", - "description": "

Name of the country

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "stations.0.plays", - "description": "

Total plays for the radio station across all dates

" - }, - { - "group": "Response", - "type": "Object[]", - "optional": false, - "field": "stations.1", - "description": "

Not actually an object property name, "1" represents the 1-indexed element of each station item, and is an array of air dates with play counts for each day

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "stations.1.air_date", - "description": "

date in ISO Date Format (e.g. "2020-01-11T08:00:00.000Z")

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "stations.1.count", - "description": "

Count of plays on this air_date

" - }, - { - "group": "Response", - "type": "Object[]", - "optional": false, - "field": "tracks", - "description": "

An array of track items, where each track item itself is a 2-item array where the first element is an object containing track metadata info, and the second element is an array of air dates with a count of plays for each date

" - }, - { - "group": "Response", - "type": "Object", - "optional": false, - "field": "tracks.0", - "description": "

Not actually an object property name, "0" represents the 0-indexed element of each track item, and is an object with track metadata info

" - }, - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "tracks.0.id", - "description": "

Chartmetric track id

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "tracks.0.name", - "description": "

Name of the track

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "tracks.0.isrc", - "description": "

ISRC of the track

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "tracks.0.plays", - "description": "

Total plays for the track across all dates

" - }, - { - "group": "Response", - "type": "Object[]", - "optional": false, - "field": "tracks.1", - "description": "

Not actually an object property name, "1" represents the 1-indexed element of each track item, and is an array of air dates with play counts for each day

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "tracks.1.air_date", - "description": "

date in ISO Date Format (e.g. "2020-01-11T08:00:00.000Z")

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "tracks.1.count", - "description": "

Count of plays on this air_date

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"countries\": [\n [\n {\n \"code2\": \"US\",\n \"name\": \"United States\",\n \"plays\": 76376,\n \"plays_percent\": 0.43664886744342934\n },\n [\n {\n \"air_date\": \"2023-01-01T00:00:00.000Z\",\n \"count\": 932\n },\n {\n \"air_date\": \"2023-01-02T00:00:00.000Z\",\n \"count\": 924\n },\n ...\n {\n \"air_date\": \"2023-04-06T00:00:00.000Z\",\n \"count\": 828\n }\n ]\n ],\n ...\n [\n {\n \"code2\": \"DE\",\n \"name\": \"Germany\",\n \"plays\": 14383,\n \"plays_percent\": 0.08222898109928307\n },\n [\n {\n \"air_date\": \"2023-01-01T00:00:00.000Z\",\n \"count\": 187\n },\n {\n \"air_date\": \"2023-01-02T00:00:00.000Z\",\n \"count\": 187\n },\n ...\n {\n \"air_date\": \"2023-04-06T00:00:00.000Z\",\n \"count\": 152\n }\n ]\n ]\n ],\n \"cities\": [\n [\n {\n \"id\": 2698,\n \"name\": \"Berlin\",\n \"code2\": \"DE\",\n \"country\": \"Germany\",\n \"lat\": 52.5218,\n \"lng\": 13.4015,\n \"plays\": 6168,\n \"plays_percent\": 0.03526304355283168\n },\n [\n {\n \"air_date\": \"2023-01-01T00:00:00.000Z\",\n \"count\": 89\n },\n {\n \"air_date\": \"2023-01-02T00:00:00.000Z\",\n \"count\": 81\n },\n ...\n {\n \"air_date\": \"2023-04-06T00:00:00.000Z\",\n \"count\": 58\n }\n ]\n ],\n [\n {\n \"id\": 3814,\n \"name\": \"Johor Bahru\",\n \"code2\": \"MY\",\n \"country\": \"Malaysia\",\n \"lat\": 1.48,\n \"lng\": 103.73,\n \"plays\": 1,\n \"plays_percent\": 0.000005717095258241193\n },\n [\n {\n \"air_date\": \"2023-01-01T00:00:00.000Z\",\n \"count\": 1\n }\n ]\n ],\n ...\n [\n {\n \"id\": 15512,\n \"name\": \"Brooklyn\",\n \"code2\": \"US\",\n \"country\": \"United States\",\n \"lat\": 41.787,\n \"lng\": -71.9543,\n \"plays\": 1,\n \"plays_percent\": 0.000005717095258241193\n },\n [\n {\n \"air_date\": \"2023-03-08T00:00:00.000Z\",\n \"count\": 1\n }\n ]\n ]\n ],\n \"stations\": [\n [\n {\n \"id\": 196100,\n \"name\": \"ENERGY Romantic\",\n \"code2\": \"DE\",\n \"city\": \"Berlin\",\n \"country\": \"Germany\",\n \"plays\": 1772,\n \"plays_percent\": 0.010130692797603393\n },\n [\n {\n \"air_date\": \"2023-01-01T00:00:00.000Z\",\n \"count\": 22\n },\n {\n \"air_date\": \"2023-01-02T00:00:00.000Z\",\n \"count\": 24\n },\n ...\n {\n \"air_date\": \"2023-04-06T00:00:00.000Z\",\n \"count\": 17\n }\n ]\n ],\n [\n {\n \"id\": 196940,\n \"name\": \"ENERGY Love\",\n \"code2\": \"DE\",\n \"city\": \"Berlin\",\n \"country\": \"Germany\",\n \"plays\": 1759,\n \"plays_percent\": 0.010056370559246259\n },\n [\n {\n \"air_date\": \"2023-01-01T00:00:00.000Z\",\n \"count\": 29\n },\n {\n \"air_date\": \"2023-01-02T00:00:00.000Z\",\n \"count\": 23\n },\n ...\n {\n \"air_date\": \"2023-04-06T00:00:00.000Z\",\n \"count\": 16\n }\n ]\n ],\n ...\n [\n {\n \"id\": 236166,\n \"name\": \"AFN 360 Powertalk\",\n \"code2\": \"JP\",\n \"city\": \"Tokyo\",\n \"country\": \"Japan\",\n \"plays\": 1,\n \"plays_percent\": 0.000005717095258241193\n },\n [\n {\n \"air_date\": \"2023-03-28T00:00:00.000Z\",\n \"count\": 1\n }\n ]\n ],\n [\n {\n \"id\": 190611,\n \"name\": \"NRJ Norway\",\n \"code2\": \"NO\",\n \"city\": \"Oslo\",\n \"country\": \"Norway\",\n \"plays\": 1,\n \"plays_percent\": 0.000005717095258241193\n },\n [\n {\n \"air_date\": \"2023-01-01T00:00:00.000Z\",\n \"count\": 1\n }\n ]\n ]\n ],\n \"tracks\": [\n [\n {\n \"id\": 63926722,\n \"name\": \"Easy On Me\",\n \"isrc\": \"USSM12105970\",\n \"plays\": 44623,\n \"plays_percent\": 0.25511394170849677\n },\n [\n {\n \"air_date\": \"2023-01-01T00:00:00.000Z\",\n \"count\": 703\n },\n {\n \"air_date\": \"2023-01-02T00:00:00.000Z\",\n \"count\": 624\n },\n ...\n {\n \"air_date\": \"2023-04-06T00:00:00.000Z\",\n \"count\": 419\n }\n ]\n ],\n [\n {\n \"id\": 12569904,\n \"name\": \"Rolling in the Deep - Acapella\",\n \"isrc\": \"GBBKS1000427\",\n \"plays\": 29604,\n \"plays_percent\": 0.16924888802497226\n },\n [\n {\n \"air_date\": \"2023-01-01T00:00:00.000Z\",\n \"count\": 273\n },\n {\n \"air_date\": \"2023-01-02T00:00:00.000Z\",\n \"count\": 361\n },\n ...\n {\n \"air_date\": \"2023-04-06T00:00:00.000Z\",\n \"count\": 338\n }\n ]\n ],\n ...\n [\n {\n \"id\": 12569499,\n \"name\": \"Hometown Glory - Live at Hotel Cafe\",\n \"isrc\": \"GBBKS0800532\",\n \"plays\": 1,\n \"plays_percent\": 0.000005717095258241193\n },\n [\n {\n \"air_date\": \"2023-02-18T00:00:00.000Z\",\n \"count\": 1\n }\n ]\n ],\n [\n {\n \"id\": 16873418,\n \"name\": \"Take It All (Live)\",\n \"isrc\": \"GBBKS1100201\",\n \"plays\": 1,\n \"plays_percent\": 0.000005717095258241193\n },\n [\n {\n \"air_date\": \"2023-03-22T00:00:00.000Z\",\n \"count\": 1\n }\n ]\n ]\n ]\n }\n}", - "type": "json" - } - ] - }, - "filename": "src/Routers/apiRouter/docs/radio.docs.ts", - "groupTitle": "Radio" - }, - { - "type": "get", - "url": "/api/radio/:type/:id/airplay-totals/:entity", - "title": "Total Airplays by Entity", - "version": "1.0.0", - "name": "GetRadioAirplayTotalsEntity", - "description": "

This route returns the number of times this album, track or 'artist' has played on the radio across all markets Charmetric follows.

Results can be retrieved by country, city, station, or track.

", - "group": "Radio", - "parameter": { - "fields": { - "Path Parameters": [ - { - "group": "Path Parameters", - "type": "String", - "allowedValues": [ - "artist", - "album", - "track" - ], - "optional": false, - "field": "type", - "description": "" - }, - { - "group": "Path Parameters", - "type": "Number", - "optional": false, - "field": "id", - "description": "

Chartmetric Artist/Album/Track ID

" - }, - { - "group": "Path Parameters", - "type": "String", - "allowedValues": [ - "country", - "city", - "station", - "track" - ], - "optional": false, - "field": "entity", - "description": "

Entity to retrieve results by

" - } - ], - "Query Parameters": [ - { - "group": "Query Parameters", - "type": "Date", - "optional": false, - "field": "since", - "description": "

Start date in ISO date format (e.g. "2017-03-25"). This parameter is required.

" - }, - { - "group": "Query Parameters", - "type": "Number", - "optional": true, - "field": "station", - "description": "

Chartmetric Radio Station ID to filter play counts by a single radio station.

" - }, - { - "group": "Query Parameters", - "type": "Number", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned. Maximum 100.

" - }, - { - "group": "Query Parameters", - "type": "String", - "allowedValues": [ - "ISO", - "UNIX" - ], - "optional": true, - "field": "dateFormat", - "defaultValue": "ISO", - "description": "

Format for retrieved dates. ISO results in dates like 01-12-2023, UNIX results in dates like 1672549200 (UTC-based epoch)

" - } - ] - } - }, - "examples": [ - { - "title": "Artist plays by Country:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/radio/artist/3079/airplay-totals/country?since=2023-01-01&limit=20", - "type": "curl" - }, - { - "title": "Album plays by City:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/radio/album/13776687/airplay-totals/city?since=2022-05-09", - "type": "curl" - }, - { - "title": "Track plays by Station:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/radio/track/50621806/airplay-totals/station?since=2023-02-03", - "type": "curl" - }, - { - "title": "Artist plays by Track:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/radio/artist/206557/airplay-totals/track?since=2023-03-16", - "type": "curl" - } - ], - "success": { - "fields": { - "Shared Response Fields": [ - { - "group": "Shared Response Fields", - "type": "Object[]", - "optional": false, - "field": "obj", - "description": "

Top level array of results

" - }, - { - "group": "Shared Response Fields", - "type": "Number", - "optional": false, - "field": "obj.plays", - "description": "

Number of plays over the given period

" - }, - { - "group": "Shared Response Fields", - "type": "Number", - "optional": false, - "field": "obj.plays_percent", - "description": "

Percentage of plays that occcurred in this country out of all that occurred over the given period

" - }, - { - "group": "Shared Response Fields", - "type": "Object[]", - "optional": false, - "field": "obj.plays_trend", - "description": "

Array of dates and corresponding play counts for those dates

" - }, - { - "group": "Shared Response Fields", - "type": "String", - "optional": false, - "field": "obj.plays_trend.air_date", - "description": "

Date on which count plays occurred

" - }, - { - "group": "Shared Response Fields", - "type": "String", - "optional": false, - "field": "obj.plays_trend.count", - "description": "

Number of plays that occurred on air_date

" - } - ], - "Country Response Fields": [ - { - "group": "Country Response Fields", - "type": "Object[]", - "optional": false, - "field": "obj", - "description": "

Top level array of results

" - }, - { - "group": "Country Response Fields", - "type": "String", - "optional": false, - "field": "obj.code2", - "description": "

2-letter country code

" - }, - { - "group": "Country Response Fields", - "type": "String", - "optional": false, - "field": "obj.name", - "description": "

Country name

" - } - ], - "City Response Fields": [ - { - "group": "City Response Fields", - "type": "Object[]", - "optional": false, - "field": "obj", - "description": "

Top level array of results

" - }, - { - "group": "City Response Fields", - "type": "Number", - "optional": false, - "field": "obj.id", - "description": "

Chartmetric City ID

" - }, - { - "group": "City Response Fields", - "type": "String", - "optional": false, - "field": "obj.name", - "description": "

City name

" - }, - { - "group": "City Response Fields", - "type": "String", - "optional": false, - "field": "obj.code2", - "description": "

2-letter country code

" - }, - { - "group": "City Response Fields", - "type": "String", - "optional": false, - "field": "obj.country", - "description": "

Country name

" - }, - { - "group": "City Response Fields", - "type": "Number", - "optional": false, - "field": "obj.lat", - "description": "

Latitude of the city

" - }, - { - "group": "City Response Fields", - "type": "Number", - "optional": false, - "field": "obj.lng", - "description": "

Longitude of the city

" - } - ], - "Station Response Fields": [ - { - "group": "Station Response Fields", - "type": "Object[]", - "optional": false, - "field": "obj", - "description": "

Top level array of results

" - }, - { - "group": "Station Response Fields", - "type": "Number", - "optional": false, - "field": "obj.id", - "description": "

Chartmetric Station ID

" - }, - { - "group": "Station Response Fields", - "type": "String", - "optional": false, - "field": "obj.name", - "description": "

Station name

" - }, - { - "group": "Station Response Fields", - "type": "String", - "optional": false, - "field": "obj.code2", - "description": "

2-letter country code

" - }, - { - "group": "Station Response Fields", - "type": "String", - "optional": false, - "field": "obj.city", - "description": "

City name

" - }, - { - "group": "Station Response Fields", - "type": "String", - "optional": false, - "field": "obj.country", - "description": "

Country name

" - } - ], - "Track Response Fields": [ - { - "group": "Track Response Fields", - "type": "Object[]", - "optional": false, - "field": "obj", - "description": "

Top level array of results

" - }, - { - "group": "Track Response Fields", - "type": "Number", - "optional": false, - "field": "obj.id", - "description": "

Chartmetric Track ID

" - }, - { - "group": "Track Response Fields", - "type": "String", - "optional": false, - "field": "obj.name", - "description": "

Track name

" - }, - { - "group": "Track Response Fields", - "type": "String", - "optional": false, - "field": "obj.isrc", - "description": "

ISRC code

" - } - ] - }, - "examples": [ - { - "title": "Country Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"code2\": \"US\",\n \"name\": \"United States\",\n \"plays\": 81275,\n \"plays_percent\": 0.43441659094553425,\n \"plays_trend\": [\n {\n \"air_date\": \"2023-01-01\",\n \"count\": 916\n },\n ...\n ]\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "City Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 7052,\n \"name\": \"San Francisco\",\n \"code2\": \"US\",\n \"country\": \"United States\",\n \"lat\": 37.7562,\n \"lng\": -122.443,\n \"plays\": 1314,\n \"plays_percent\": 0.013084261047935794,\n \"plays_trend\": [\n {\n \"air_date\": \"2023-01-01\",\n \"count\": 9\n },\n ...\n ],\n }\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Station Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 190585,\n \"name\": \"KALV-FM\",\n \"code2\": \"US\",\n \"city\": \"Phoenix\",\n \"country\": \"United States\",\n \"plays\": 733,\n \"plays_percent\": 0.007330659759378344,\n \"plays_trend\": [\n {\n \"air_date\": \"2023-01-01\",\n \"count\": 8\n },\n ...\n ]\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Track Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 83953671,\n \"name\": \"Left and Right (Feat. Jung Kook of BTS)\",\n \"isrc\": \"USAT22205563\",\n \"plays\": 12425,\n \"plays_percent\": 0.21805131445017725,\n \"plays_trend\": [\n {\n \"air_date\": \"2023-01-01\",\n \"count\": 244\n },\n ...\n ]\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "filename": "src/Routers/apiRouter/docs/radio.docs.ts", - "groupTitle": "Radio" - }, - { - "type": "get", - "url": "/api/radio/:type/:id/airplays", - "title": "Airplay information in Time Series", - "version": "1.1.0", - "name": "GetRadioAirplays", - "description": "

Get the time series information for how many times this 'album', 'track' or 'artist' has played on the radio

", - "group": "Radio", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Number", - "optional": false, - "field": "id", - "description": "

Chartmetric type object-ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "artist", - "album", - "track" - ], - "optional": false, - "field": "type", - "description": "" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "since", - "description": "

Start date in ISO date format (e.g. "2017-03-25")

" - } - ] - } - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/radio/artist/236/airplays?since=2020-02-20", - "type": "curl" - } - ], - "success": { - "fields": { - "Response": [ - { - "group": "Response", - "type": "Array[]", - "optional": false, - "field": "obj", - "description": "

array of airplay_data

" - }, - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "obj.air_date", - "description": "

timestamp as unix epoch

" - }, - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "obj.spins", - "description": "

number of plays(spins) of this track on timestamp

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"air_date\": 1665964800000,\n \"spins\": 6\n },\n {\n \"air_date\": 1666051200000,\n \"spins\": 7\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "filename": "src/Routers/apiRouter/docs/radio.docs.ts", - "groupTitle": "Radio" - }, - { - "type": "get", - "url": "/api/radio/:type/:id/broadcast-markets", - "title": "Broadcast Market play counts", - "version": "1.1.0", - "name": "GetRadioBroadcastMarketData", - "description": "

Get the number of times this 'album', 'track' or 'artist' has played on the radio for all broadcast markets

", - "group": "Radio", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "artist", - "album", - "track" - ], - "optional": false, - "field": "type", - "description": "" - }, - { - "group": "Parameter", - "type": "Number", - "optional": false, - "field": "id", - "description": "

Chartmetric type ID

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "since", - "description": "

Start date in ISO date format (e.g. "2017-03-25"). This parameter is required.

" - } - ] - } - }, - "examples": [ - { - "title": "Example usage:", - "content": "\ncurl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/radio/artist/236/broadcast-markets?since=2020-02-02", - "type": "curl" - } - ], - "success": { - "fields": { - "Response": [ - { - "group": "Response", - "type": "Object[]", - "optional": false, - "field": "countryRatios", - "description": "" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "countryRatios.market", - "description": "

Market Name

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "countryRatios.market_count_data", - "description": "

Total Count in market

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "countryRatios.code2", - "description": "

2-Letter Country Code

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "countryRatios.total_market_count_data", - "description": "" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "countryRatios.count_of_stations", - "description": "" - }, - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "countryRatios.population", - "description": "" - }, - { - "group": "Response", - "type": "Float", - "optional": false, - "field": "countryRatios.lat", - "description": "

Latitude

" - }, - { - "group": "Response", - "type": "Float", - "optional": false, - "field": "countryRatios.lng", - "description": "

Longitude

" - }, - { - "group": "Response", - "type": "Float", - "optional": false, - "field": "countryRatios.count", - "description": "" - }, - { - "group": "Response", - "type": "Object[]", - "optional": false, - "field": "cityRatios", - "description": "" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "cityRatios.market", - "description": "

Market Name

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "cityRatios.display_name", - "description": "

Market Display Name

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "cityRatios.code2", - "description": "

2-Letter Country Code

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "cityRatios.market_count_data", - "description": "

Total Count in market

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "cityRatios.country", - "description": "

Country name

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "cityRatios.total_market_count_data", - "description": "" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "cityRatios.count_of_stations", - "description": "" - }, - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "cityRatios.city_id", - "description": "" - }, - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "cityRatios.population", - "description": "" - }, - { - "group": "Response", - "type": "Float", - "optional": false, - "field": "cityRatios.lat", - "description": "

Latitude

" - }, - { - "group": "Response", - "type": "Float", - "optional": false, - "field": "cityRatios.lng", - "description": "

Longitude

" - }, - { - "group": "Response", - "type": "Float", - "optional": false, - "field": "cityRatios.count", - "description": "" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"countryRatios\": [\n {\n \"market\": \"Malaysia\",\n \"market_count_data\": \"1155\",\n \"code2\": \"MY\",\n \"total_market_count_data\": \"63226\",\n \"count_of_stations\": \"5\",\n \"population\": 22244000,\n \"lat\": 4.210484,\n \"lng\": 101.975766,\n \"count\": 1.82678012210167\n },\n ...\n ],\n \"cityRatios\": [\n {\n \"market\": \"Dayton\",\n \"display_name\": \"Dayton (United States)\",\n \"code2\": \"US\",\n \"market_count_data\": \"300\",\n \"country\": \"United States\",\n \"total_market_count_data\": \"9778\",\n \"count_of_stations\": \"1\",\n \"city_id\": 6869,\n \"population\": 5449,\n \"lat\": 39.1128,\n \"lng\": -84.464,\n \"count\": 3.06811208836163\n },\n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "filename": "src/Routers/apiRouter/docs/radio.docs.ts", - "groupTitle": "Radio" - }, - { - "type": "get", - "url": "/api/radio/station-list", - "title": "Get Radio Station List for Country", - "version": "1.1.0", - "name": "GetRadioStationListForCountry", - "description": "

Get the list of stations along with metadata for a given country

", - "group": "Radio", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "code2", - "description": "

2-Letter Country Code

" - } - ] - } - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/radio/station-list?code2=US", - "type": "curl" - } - ], - "success": { - "fields": { - "Response": [ - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "id", - "description": "

Chartmetric Radio Station ID

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "name", - "description": "

Radio Station Name

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "genre", - "description": "

Radio Station Genre

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "frequency", - "description": "

Radio Station Frequency

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "market", - "description": "

Radio Station Market

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "broadcast_area", - "description": "

Radio Station Broadcast Area

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "country", - "description": "

Radio Station Country

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "station_state", - "description": "

Radio Station State Code

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Radio Station Image Link

" - }, - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "aqh", - "description": "

Radio Station AQH (average quarter-hour persons): The amount of unique listeners in a 15-minute period listening for >5 minutes; only available for US stations

" - }, - { - "group": "Response", - "type": "String", - "optional": false, - "field": "platform", - "description": "

Radio Station Platform

" - }, - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "facebook_likes", - "description": "

Radio Station Facebook Likes

" - }, - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "instagram_followers", - "description": "

Radio Station Instagram Followers

" - }, - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "twitter_followers", - "description": "

Radio Station Twitter Followers

" - }, - { - "group": "Response", - "type": "Number", - "optional": false, - "field": "wikipedia_views", - "description": "

Radio Station Wikipedia Views

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n {\n \"obj\": [\n {\n \"id\": 214403,\n \"name\": \"KBIG 104.3 MYfm\",\n \"genre\": \"Hot AC\",\n \"frequency\": \"104.3\",\n \"market\": \"Los Angeles\",\n \"broadcast_area\": \"Los Angeles\",\n \"country\": \"United States\",\n \"station_state\": \"CA\",\n \"image_url\": \"https://storage.googleapis.com/cm-app-assets/radio-images/2627_KBIG_FM.png\",\n \"aqh\": 65700,\n \"platform\": \"Terrestrial\",\n \"facebook_likes\": null,\n \"instagram_followers\": 33094,\n \"twitter_followers\": 27228,\n \"wikipedia_views\": 44\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "filename": "src/Routers/apiRouter/docs/radio.docs.ts", - "groupTitle": "Radio" - }, - { - "type": "get", - "url": "/api/playlist/:platform/:id/similarplaylists", - "title": "Get 100 Similar Playlists", - "name": "GetSimilarPlaylists", - "version": "1.0.0", - "group": "Recommendation", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "soundcloud" - ], - "optional": false, - "field": "platform", - "description": "

Streaming platform type

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "indie", - "description": "

If the playlist was curated by major labels (Spotify & SoundCloud only type)

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist_id", - "description": "

Spotify playlist ID (e.g. '2nmSvIDk33WaJCN5XzU7Wx')

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Playlist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Image url

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "followers", - "description": "

Playlist follower count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "likes", - "description": "

Playlist like count

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "genre", - "description": "

Genre of playlist

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "permalink", - "description": "

Url string ID for Playlist (SoundCloud)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "owner_name", - "description": "

Curators name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "owner_permalink", - "description": "

Url string ID for Curator (SoundCloud)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "user_id", - "description": "

Spotify user ID

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "owner_id", - "description": "

Chartmetric curator ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "tags", - "description": "

tags

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "tags.id", - "description": "

Tags id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tags.name", - "description": "

Tags name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tags.type", - "description": "

Type of tags

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tags.source", - "description": "

source

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tags.url", - "description": "

Playlist image URL

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "tags.created_at", - "description": "

Created date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "tags.modified_at", - "description": "

Modified date

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 1816590,\n \"playlist_id\": \"37i9dQZF1DX8C9xQcOrE6T\",\n \"name\": \"Disney Hits\",\n \"image_url\": \"https://i.scdn.co/image/ab67706f000000034fb035ee54262509612d6900\",\n \"followers\": 3525555,\n \"owner_name\": \"Spotify\",\n \"user_id\": \"spotify\",\n \"owner_id\": 2,\n \"tags\": [\n {\n \"id\": 12463,\n \"name\": \"hollywood\",\n \"source\": \"spotify\",\n \"type\": \"genre\",\n \"url\": null,\n \"created_at\": null,\n \"modified_at\": null\n },\n {\n \"id\": 25588,\n \"name\": \"movie tunes\",\n \"source\": \"spotify\",\n \"type\": \"genre\",\n \"url\": null,\n \"created_at\": null,\n \"modified_at\": null\n }, ...\n ]\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/playlist/spotify/179228/similarplaylists", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/playlist.docs.ts", - "groupTitle": "Recommendation" - }, - { - "type": "get", - "url": "/api/SNS/deepSocial/cm_artist/:id/instagram", - "title": "Get Artist Instagram Top Posts and Reels", - "version": "1.0.0", - "name": "GetArtistInstagramTopPostsAndReels", - "description": "

Returns Top 10 Instagram posts and reels each for a given artist.

", - "group": "SNS", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric Artist Id.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "top_posts", - "description": "

List of top posts (max 10).

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "top_reels", - "description": "

List of top reels (max 10).

" - } - ], - "top_posts/top_reels Object Fields": [ - { - "group": "top_posts/top_reels Object Fields", - "type": "String", - "optional": false, - "field": "user_id", - "description": "

Instagram user ID.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "String", - "optional": false, - "field": "username", - "description": "

Instagram username.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "String", - "optional": false, - "field": "user_picture", - "description": "

URL of the user’s profile picture.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "String", - "optional": false, - "field": "user_url", - "description": "

URL of the user’s Instagram profile.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "String", - "optional": false, - "field": "type", - "description": "

Post type. image or video.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "String", - "optional": false, - "field": "post_id", - "description": "

Unique post identifier.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "String", - "optional": false, - "field": "created", - "description": "

ISO 8601 timestamp of post creation.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "String", - "optional": false, - "field": "text", - "description": "

Caption or text content of the post.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "String", - "optional": false, - "field": "image", - "description": "

URL of the post’s main image or video thumbnail.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "String", - "optional": false, - "field": "thumbnail", - "description": "

URL of the post’s thumbnail image.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "String", - "optional": false, - "field": "link", - "description": "

Direct link to the Instagram post.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "String[]", - "optional": false, - "field": "mentions", - "description": "

List of mentioned usernames.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "Object", - "optional": false, - "field": "stat", - "description": "

Engagement statistics.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "Boolean", - "optional": false, - "field": "stat.likes_count_enabled", - "description": "

Whether like count is visible.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "Number", - "optional": false, - "field": "stat.likes", - "description": "

Number of likes.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "Number", - "optional": false, - "field": "stat.comments", - "description": "

Number of comments.

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "Number", - "optional": true, - "field": "stat.plays", - "description": "

Number of plays (for reels only).

" - }, - { - "group": "top_posts/top_reels Object Fields", - "type": "Number", - "optional": true, - "field": "stat.shares", - "description": "

Number of shares.

" - } - ] - }, - "examples": [ - { - "title": "Success Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"top_posts\": [\n {\n \"user_id\": \"1707509864\",\n \"username\": \"honeymoon\",\n \"user_picture\": \"https://imgp.sptds.icu/v2?mb0KwpL92uYofJiSjDn1%2F6peL1lBwv3s%2BUvShHERlDbL6c3yyQ1fIuJmCtJ9JHtMVxvZKY1v3cVkQdjDny1KMBqLX9g4YEnYegvawruHbC2oKUjsJ9iew%2B%2Fv5%2BPY9VCaWwq5%2BhsabX0Nc9aXCBbcsw%3D%3D\",\n \"user_url\": \"https://www.instagram.com/honeymoon\",\n \"type\": \"photo\",\n \"post_id\": \"3730445037202445518_1707509864\",\n \"created\": \"2025-09-26T21:55:24.000+00:00\",\n \"image\": \"https://imgp.sptds.icu/v2?mb0KwpL92uYofJiSjDn1%2F6peL1lBwv3s%2BUvShHERlDYZrE3fWtzaxOVpYi7joNHuPPDXAnz9dqREUyXIgPImM8R10BkSIEiXblE8pngCtVmXPObOffojJjL%2B4BuTb88OXt%2FYzeksBPYLUknr5ild2A%3D%3D\",\n \"thumbnail\": \"https://imgp.sptds.icu/v2?mb0KwpL92uYofJiSjDn1%2F6peL1lBwv3s%2BUvShHERlDYZrE3fWtzaxOVpYi7joNHuPPDXAnz9dqREUyXIgPImM8R10BkSIEiXblE8pngCtVmXPObOffojJjL%2B4BuTb88OXt%2FYzeksBPYLUknr5ild2A%3D%3D\",\n \"link\": \"https://www.instagram.com/p/DPFM_8gkhTO/\",\n \"mentions\": [\n \"jeremy.dufrene\",\n \"val.ewww\",\n \"shop_cinq\",\n \"yourgirlchuck\",\n \"ralphlauren\",\n \"tiffanyandco\"\n ],\n \"stat\": {\n \"likes_count_enabled\": true,\n \"likes\": 6717673,\n \"comments\": 40955,\n \"shares\": 301160\n }\n },\n ...\n ],\n \"top_reels\": [\n {\n \"user_id\": \"1707509864\",\n \"username\": \"honeymoon\",\n \"user_picture\": \"https://imgp.sptds.icu/v2?mb0KwpL92uYofJiSjDn1%2F6peL1lBwv3s%2BUvShHERlDbL6c3yyQ1fIuJmCtJ9JHtMVxvZKY1v3cVkQdjDny1KMBqLX9g4YEnYegvawruHbC2oKUjsJ9iew%2B%2Fv5%2BPY9VCaWwq5%2BhsabX0Nc9aXCBbcsw%3D%3D\",\n \"user_url\": \"https://www.instagram.com/honeymoon\",\n \"type\": \"video\",\n \"post_id\": \"3394225214193878190_49509959\",\n \"created\": \"2024-06-20T00:29:37.000+00:00\",\n \"text\": \"Tough\",\n \"image\": \"https://imgp.sptds.icu/v2?mb0KwpL92uYofJiSjDn1%2F6peL1lBwv3s%2BUvShHERlDZYmZ4eGQ1lFBg%2BQ9O%2FU0Lc4Nwdn1Ip05Bgk1uCTwjUpHO8DfdCBkjY%2BjdT7jKifZR7XqFUf87fwOB9B9%2B4OKEvgNlJUwVYqIiahjW32e%2BqWA%3D%3D\",\n \"thumbnail\": \"https://imgp.sptds.icu/v2?mb0KwpL92uYofJiSjDn1%2F6peL1lBwv3s%2BUvShHERlDZYmZ4eGQ1lFBg%2BQ9O%2FU0Lc4Nwdn1Ip05Bgk1uCTwjUpHO8DfdCBkjY%2BjdT7jKifZR7XqFUf87fwOB9B9%2B4OKEvgNlJUwVYqIiahjW32e%2BqWA%3D%3D\",\n \"link\": \"https://www.instagram.com/p/C8atdlTs0Su/\",\n \"stat\": {\n \"likes_count_enabled\": true,\n \"likes\": 3021396,\n \"comments\": 20994,\n \"plays\": 46374448\n }\n },\n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer ACCESS_KEY' https://api.chartmetric.com/api/SNS/deepSocial/cm_artist/3986/instagram", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/SNS.docs.ts", - "groupTitle": "SNS" - }, - { - "type": "get", - "url": "/api/genres/:id", - "title": "Get Genre By Id", - "name": "GenreById", - "version": "1.0.0", - "description": "

Get Chartmetric genre by id

", - "group": "Search", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "id", - "description": "

Genre ID

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "id", - "description": "

Chartmetric genre ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Chartmetric genre name

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"id\": 462925,\n \"name\": \"K-Pop\"\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/genres/462925", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/root.docs.ts", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "/api/genres", - "title": "Get Genre IDs and Names", - "name": "Genres", - "version": "1.0.0", - "description": "

Get Chartmetric genres. By default, it returns all genres. If the name parameter is passed, it returns only the matching genre names.

", - "group": "Search", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "name", - "description": "

Genre name. This parameter is optional. If no name is passed, we will return all available genres.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "obj", - "description": "

List of results

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.genres.id", - "description": "

Chartmetric genre ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.genres.name", - "description": "

Chartmetric genre name

" - } - ] - }, - "examples": [ - { - "title": "Success-Response (return all genres):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 462862,\n \"name\": \"Devotional & Spiritual\"\n },\n {\n \"id\": 462863,\n \"name\": \"Mandopop\"\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (search with name):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 462863,\n \"name\": \"Mandopop\"\n },\n {\n \"id\": 462865,\n \"name\": \"Cantopop\"\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage (return all genres):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/genres", - "type": "curl" - }, - { - "title": "Example usage (search with name):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/genres?name=hip-hop", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/root.docs.ts", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "/api/search", - "title": "Chartmetric Search Engine", - "name": "Search", - "version": "1.0.0", - "description": "

Search tracks, albums, artists, curators, playlists, and more with one single query.
You can also search with URLs, for example:
https://open.spotify.com/artist/7ENzCHnmJUr20nUjoZ0zZ1?si=KpNjtD0HTsiShOTM2ZuFdA
https://itunes.apple.com/us/artist/snarky-puppy/152987454
https://www.deezer.com/artist/471526?utm_source=deezer&utm_content=artist-471526&utm_term=816683587_1544480636&utm_medium=web

", - "group": "Search", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "q", - "description": "

search query

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "all", - "artists", - "tracks", - "playlists", - "curators", - "albums", - "stations", - "cities", - "songwriters" - ], - "optional": true, - "field": "type", - "defaultValue": "all", - "description": "

The type of search for the query

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "triggerCitiesOnly", - "defaultValue": "false", - "description": "

Only return trigger cities. This flag is available for both beta=true and beta=false requests.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "beta", - "defaultValue": "false", - "description": "

Enable the improved beta search engine for higher relevance and accuracy.

" - }, - { - "group": "Parameter", - "type": "String[]", - "allowedValues": [ - "cm", - "spotify", - "itunes", - "applemusic", - "deezer", - "amazon", - "youtube", - "tidal", - "soundcloud", - "acr" - ], - "optional": true, - "field": "platforms", - "defaultValue": "[]", - "description": "

Platforms to search. This parameter available only beta=true requests.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "artists", - "description": "

List of artists

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "artists.id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.isni", - "description": "

Artist isni

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "artists.sp_monthly_listeners", - "description": "

Number of Spotify Monthly Listeners

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "artists.sp_followers", - "description": "

Number of Spotify Followers

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "artists.cm_artist_score", - "description": "

Chartmetric Artist Score

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "playlists", - "description": "

List of playlists

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "playlists.spotify", - "description": "

List of playlists

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "playlists.spotify.id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.spotify.name", - "description": "

playlist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.spotify.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.spotify.owner_name", - "description": "

Curator's name

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "playlists.applemusic", - "description": "

List of playlists

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "playlists.applemusic.id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.applemusic.name", - "description": "

Playlist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.applemusic.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.applemusic.owner_name", - "description": "

Curator's name

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "playlists.deezer", - "description": "

List of playlists

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.deezer.id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.deezer.name", - "description": "

Playlist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.deezer.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.deezer.owner_name", - "description": "

Curator's name

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "playlists.amazon", - "description": "

List of playlists

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.amazon.id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.amazon.name", - "description": "

Playlist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.amazon.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.amazon.owner_name", - "description": "

Curator's name

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "playlists.youtube", - "description": "

List of playlists

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.youtube.id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.youtube.name", - "description": "

Playlist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlists.youtube.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "tracks", - "description": "

List of tracks

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tracks.id", - "description": "

Chartmetric track ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tracks.name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tracks.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tracks.isrc", - "description": "

Track isrc

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "tracks.artist_names", - "description": "

Artists name

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "curators", - "description": "

List of curators

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "curators.spotify", - "description": "

List of curators

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "curators.spotify.id", - "description": "

Chartmetric curators ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "curators.spotify.name", - "description": "

Curator's name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "curators.spotify.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "curators.applemusic", - "description": "

List of curators

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "curators.applemusic.id", - "description": "

Chartmetric curators ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "curators.applemusic.name", - "description": "

Curator's name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "curators.applemusic.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "curators.deezer", - "description": "

List of curators

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "curators.deezer.id", - "description": "

Chartmetric curators ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "curators.deezer.name", - "description": "

Curator's name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "curators.deezer.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "albums", - "description": "

List of albums

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "albums.id", - "description": "

Chartmetric album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "albums.name", - "description": "

Album name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "albums.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "albums.label", - "description": "

Music label

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "stations", - "description": "

List of stations

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "stations.id", - "description": "

Chartmetric station ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "stations.name", - "description": "

Station name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "stations.genre", - "description": "

Genre of the station

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "stations.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "stations.station_city", - "description": "

City of the station

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "stations.country", - "description": "

Country of the station

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "labels", - "description": "

List of labels

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "cities", - "description": "

List of cities

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cities.id", - "description": "

Chartmetric city ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cities.name", - "description": "

City name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cities.name_ascii", - "description": "

City name (ascii)

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cities.population", - "description": "

City population. Most often, the city with the highest population will be chosen across other APIs.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cities.country", - "description": "

Country of the city

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cities.code2", - "description": "

Country code of the city

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cities.province", - "description": "

State or province of the city

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "cities.trigger_city", - "description": "

Whether the city is considered a Trigger City

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "songwriters", - "description": "

List of songwriters

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "songwriters.name", - "description": "

Songwriter name

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "songwriters.doc_id", - "description": "

Chartmetric songwriter ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "songwriters.artistName", - "description": "

Songwriter's name as an artist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "songwriters.image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "suggestions", - "description": "

List of search result. If beta=true.

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "match_strength", - "description": "

Match strength of the search result. If beta=true.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "target", - "description": "

target of the search result. If beta=true.

" - }, - { - "group": "Success 200", - "type": "platform", - "optional": false, - "field": "platform", - "description": "

platform of the search result. If beta=true.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Name of the search result. If beta=true.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Image URL of the search result. If beta=true.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response (`beta=false`):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"artists\": [\n {\n \"id\": 579186,\n \"name\": \"Ariana\",\n \"image_url\": \"https://i.scdn.co/image/ab6761610000e5eb73979c40f2459e3e417a130b\",\n \"isni\": null,\n \"verified\": false,\n \"sp_followers\": 1413,\n \"sp_monthly_listeners\": 2558,\n \"cm_artist_score\": 223\n },\n {\n \"id\": 3963,\n \"name\": \"Ariana Grande\",\n \"image_url\": \"https://i.scdn.co/image/ab6761610000e5eb40b5c07ab77b6b1a9075fdc0\",\n \"isni\": \"0000000059110272\",\n \"verified\": false,\n \"sp_followers\": 97135043,\n \"sp_monthly_listeners\": 80733093,\n \"cm_artist_score\": 495952\n },\n ...\n ],\n \"playlists\": {\n \"spotify\": [\n {\n \"id\": 741236,\n \"name\": \"Ariana Grande: Best Of\",\n \"image_url\": \"https://i.scdn.co/image/ab67706c0000bebb4d5b3fbd576a2254f137f5e1\",\n \"owner_name\": \"arianagrandeofficial\"\n },\n {\n \"id\": 5410288,\n \"name\": \"Ariana Grande Radio\",\n \"image_url\": \"https://seeded-session-images.scdn.co/v1/img/artist/66CXWjxzNUsdJxJ2JdwvnR/en\",\n \"owner_name\": \"Spotify\"\n },\n ...\n ],\n \"applemusic\": [\n {\n \"id\": 54706,\n \"name\": \"Ariana Grande: Influences\",\n \"image_url\": \"https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/37/63/d0/3763d030-e243-6cfa-5e92-80e55ad07e5e/source/160x160SC.FPINF01.jpeg?l=en-US\",\n \"owner_name\": \"Apple Music Pop\"\n },\n {\n \"id\": 108005,\n \"name\": \"Ariana Grande Video Essentials\",\n \"image_url\": \"https://is4-ssl.mzstatic.com/image/thumb/Features124/v4/4d/5a/02/4d5a02d4-7c55-1c6b-2126-0413d4c90a06/source/160x160cc.jpeg\",\n \"owner_name\": \"Apple Music Pop\"\n },\n ...\n ],\n \"deezer\": [\n {\n \"id\": \"1147053693\",\n \"name\": \"Ariana Grande Selection\",\n \"image_url\": \"https://api.deezer.com/playlist/1147053693/image\",\n \"owner_name\": \"Tigo El Salvador\"\n },\n {\n \"id\": \"1924513302\",\n \"name\": \"Ariana Grande Essential, a playlist by uDiscover o\",\n \"image_url\": \"https://api.deezer.com/playlist/1924513302/image\",\n \"owner_name\": \"uDiscover Music\"\n },\n ...\n ],\n \"amazon\": [\n {\n \"id\": \"B07Q752WM6\",\n \"name\": \"Ariana Grande: Greatest Hits\",\n \"image_url\": \"https://m.media-amazon.com/images/I/81ZDHSDYs1L.jpg\",\n \"owner_name\": \"Amazon-Musikredaktion\"\n }\n ],\n \"youtube\": []\n },\n \"tracks\": [\n {\n \"id\": \"27590886\",\n \"cm_track\": \"27590886\",\n \"artist_names\": [\n \"Tony Anderson\"\n ],\n \"artist\": [\n {\n \"id\": 210894,\n \"name\": \"Tony Anderson\"\n }\n ],\n \"image_url\": \"https://i.scdn.co/image/9fa1a27e9e43b48a822e23285d1a13b64f74d311\",\n \"isrc\": \"GBKPL1977961\",\n \"name\": \"Ariana\",\n \"album\": [\n {\n \"id\": 5460497,\n \"name\": \"Ariana\",\n \"release_date\": \"2019-03-12\"\n }\n ]\n },\n {\n \"id\": \"61597437\",\n \"cm_track\": \"61597437\",\n \"artist_names\": [\n \"Jé Santiago\",\n \"Dfideliz\"\n ],\n \"artist\": [\n {\n \"id\": 398648,\n \"name\": \"Jé Santiago\"\n },\n {\n \"id\": 653155,\n \"name\": \"Dfideliz\"\n }\n ],\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b2737b0314e32f28c3aa6bcab762\",\n \"isrc\": \"BCP4L2100001\",\n \"name\": \"Ariana\",\n \"album\": [\n {\n \"id\": 15091594,\n \"name\": \"Ariana\",\n \"release_date\": \"2021-09-24\"\n }\n ]\n },\n ...\n ],\n \"curators\": {\n \"spotify\": [\n {\n \"id\": 59081,\n \"name\": \"arianagrandeofficial\",\n \"image_url\": null\n },\n {\n \"id\": 14485951,\n \"name\": \"Ariana Oliveira\",\n \"image_url\": \"https://i.scdn.co/image/ab6775700000ee858a2ba84d0838c3990c4337a6\"\n },\n ...\n ],\n \"applemusic\": [],\n \"deezer\": []\n },\n \"albums\": [\n {\n \"id\": 15091594,\n \"name\": \"Ariana\",\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b2737b0314e32f28c3aa6bcab762\",\n \"label\": \"Dfideliz\"\n },\n {\n \"id\": 14170492,\n \"name\": \"ARIANA\",\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b273b5322626aa7464a83fd9bcaa\",\n \"label\": \"brisa\"\n },\n ...\n ],\n \"stations\": [],\n \"labels\": [],\n \"cities\": [\n {\n \"id\": 16166,\n \"name\": \"Ariana\",\n \"name_ascii\": \"Ariana\",\n \"population\": 97687,\n \"country\": \"Tunisia\",\n \"code2\": \"TN\",\n \"province\": null,\n \"trigger_city\": false\n },\n {\n \"id\": 415375,\n \"name\": \"Ariana\",\n \"name_ascii\": \"Ariana\",\n \"population\": 21,\n \"country\": \"Italy\",\n \"code2\": \"IT\",\n \"province\": null,\n \"trigger_city\": false\n }\n ],\n \"songwriters\": [\n {\n \"name\": \"Nicole Ariana\",\n \"doc_id\": 1564636,\n \"artistName\": \"Nicole Ariana\",\n \"image_url\": null\n },\n {\n \"name\": \"Ariana Jalia\",\n \"doc_id\": 14645385,\n \"artistName\": \"Ariana Jalia\",\n \"image_url\": null\n },\n ...\n ]\n }\n}", - "type": "json" - }, - { - "title": "Success-Response (`beta=true`):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"suggestions\": [\n {\n \"match_strength\": 22516.875,\n \"target\": \"track\",\n \"platform\": \"cm\",\n \"id\": 34728848,\n \"name\": \"Save Your Tears (with Ariana Grande) (Remix)\",\n \"imageUrl\": \"https://i.scdn.co/image/ab67616d0000b2737a5b4e0ebe93011c797193ea\"\n },\n ...\n ]\n }\n}", - "type": "json" - }, - { - "title": "Success-Response (`triggerCitiesOnly=true`):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"suggestions\": [\n {\n \"match_strength\": 1406.25,\n \"target\": \"city\",\n \"platform\": \"cm\",\n \"id\": 5602,\n \"name\": \"Seoul\",\n \"imageUrl\": \"https://storage.googleapis.com/cm-app-assets/city-images/seoul-5602.jpg\"\n }\n ],\n \"idDetected\": false\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/search?q=Ariana&limit=10", - "type": "curl" - }, - { - "title": "Example usage (`beta=true`):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/search?q=Ariana&limit=10&beta=true&platforms[]=spotify&type=tracks", - "type": "curl" - }, - { - "title": "Example usage (`triggerCitiesOnly=true`):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com//api/search?q=seoul&limit=10&type=cities&triggerCitiesOnly=true&beta=true", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/root.docs.ts", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "/api/cities", - "title": "Get City Info", - "version": "1.0.0", - "name": "SearchCity", - "description": "

Get chartmetric city info like city ids, city name, etc. for a country code.

", - "group": "Search", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "country_code", - "description": "

2-letter Country code is required.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "city_id", - "description": "

Chartmetric city ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "city_name", - "description": "

City Name

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "latitude", - "description": "

City latitude coordinate

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "longitude", - "description": "

City longitude coordinate

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "population", - "description": "

City population

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "province", - "description": "

City province

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "locality", - "description": "

City locality

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "country", - "description": "

Country name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

City image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "iso3", - "description": "

Country ISO-3 code

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"city_id\": 11778,\n \"city_name\": \"Culpeper\",\n \"latitude\": 38.4705,\n \"longitude\": -78.0001,\n \"population\": 19986,\n \"province\": null,\n \"locality\": \"Culpeper\",\n \"country\": \"United States\",\n \"image_url\": null,\n \"iso3\": \"USA\"\n },\n {\n \"city_id\": 11779,\n \"city_name\": \"Miamisburg\",\n \"latitude\": 39.6323,\n \"longitude\": -84.2725,\n \"population\": 19985,\n \"province\": null,\n \"locality\": \"Miamisburg\",\n \"country\": \"United States\",\n \"image_url\": null,\n \"iso3\": \"USA\"\n },\n ...\n\t ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/cities?country_code=US", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/root.docs.ts", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "/api/search/social", - "title": "Chartmetric Social Search", - "name": "SocialSearch", - "version": "1.0.0", - "description": "

Search for artists, curators, radio/SiriusXM stations, and songwriters by their social media URLs.

Note that this endpoint is intended for use with social media URL searches only. If you're looking for an artist, track, or album by their DSP link, use the corresponding /api/:entity/:type/:id/get-ids endpoint instead. Therefore, this endpoint will not consistently return results for DSP links like Spotify, Apple Music, etc.

", - "group": "Search", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "url", - "description": "

URL to search for.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "all", - "artists", - "curators", - "radio", - "siriusxm", - "songwriters" - ], - "optional": true, - "field": "type", - "defaultValue": "all", - "description": "

The type of search for the query

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "obj", - "description": "" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.type", - "description": "

The type of the search result

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "obj.id", - "description": "

The Chartmetric ID of the search result

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.name", - "description": "

The name of the search result (only supported for type=artists currently)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "obj.image_url", - "description": "

The image URL of the search result (only supported for type=artists currently)

" - } - ] - }, - "examples": [ - { - "title": "Instagram", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 2762,\n \"type\": \"artist\",\n \"name\": \"Taylor Swift\",\n \"image_url\": \"https://i.scdn.co/image/ab67616100005174f173136b94ae2f75f49f8b2b\"\n }\n ]\n}", - "type": "json" - }, - { - "title": "TikTok", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 206557,\n \"type\": \"artist\",\n \"name\": \"BTS\",\n \"image_url\": \"https://i.scdn.co/image/ab67616100005174d642648235ebf3460d2d1f6a\"\n }\n ]\n}", - "type": "json" - }, - { - "title": "SiriusXM", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": \"siriushits1\",\n \"type\": \"siriusxm\",\n \"name\": null,\n \"image_url\": null\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage (Instagram):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/search/social?url=https://www.instagram.com/taylorswift/", - "type": "curl" - }, - { - "title": "Example usage (TikTok):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/search/social?url=https://www.tiktok.com/@bts_official_bighit", - "type": "curl" - }, - { - "title": "Example usage (SiriusXM):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/search/social?url=https://www.facebook.com/siriusxmhits1&type=siriusxm", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/root.docs.ts", - "groupTitle": "Search" - }, - { - "type": "get", - "url": "/api/track/:id/:platform/:status/playlists", - "title": "Playlists", - "name": "GetPlaylistsByTracks", - "version": "1.0.0", - "description": "

Get current or past playlists for a track.

sortColumn options for each platform

Each platform and status combination has different sorting options due to the variety of data available. Valid combinations are provided in the below table for your convenience:

platform status Valid sortColumn values
spotify current added_at, fdiff_month, followers (default), name, position, peak_position, track, code2
spotify past added_at, fdiff_month, followers (default), name, position, peak_position, removed_at, track, code2
applemusic current added_at (default), name, position, peak_position
applemusic past added_at, name, position, peak_position, removed_at (default)
deezer current added_at (default), fdiff_month, followers, name, peak_position
deezer past added_at, fdiff_month, followers, name, peak_position, removed_at (default)
amazon current added_at (default), countries, name, peak_position
amazon past added_at, peak_position, removed_at (default)

Note that if no sortColumn value is passed the (default) option will be used.

Parameter options for each platform

Each platform supports different parameters. Valid parameters are provided in the below table for your convenience:

Parameters spotify applemusic deezer amazon
editorial
editorialBrand
personalized
deezerPartner
chart
thisIs
hundredPercent
newMusicFriday
radio
fullyPersonalized
brand
majorCurator
musicBrand
nonMusicBrand
popularIndie
indie
audiobook
personalityArtist

Allowed Parameters Summary:

  • spotify: editorial, personalized, chart, thisIs, newMusicFriday, radio, fullyPersonalized, brand, majorCurator, popularIndie, indie, audiobook
  • applemusic: editorial, editorialBrand, chart, radio, musicBrand, nonMusicBrand, indie, personalityArtist
  • deezer: editorial, deezerPartner, chart, hundredPercent, brand, majorCurator, popularIndie, indie
  • amazon: No allowed flags

Important: When no parameters specified, this API can return an empty array. API users should specify parameters explicitly to get data.

Parameters are not mutually exclusive.

Specify other parameters as FALSE to retrieve data only specific to one parameter. (ex. only newMusicFriday)

", - "group": "Track", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric track ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "applemusic", - "deezer", - "amazon" - ], - "optional": false, - "field": "platform", - "description": "

Streaming platform type

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "current", - "past" - ], - "optional": false, - "field": "status", - "description": "

Playlist status The default value will be 6 months prior to the date of call.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "since", - "defaultValue": "180 days ago", - "description": "

begining of the added/removed date, in ISO date format (e.g. "2017-03-25"). The default value will be 6 months prior to the date of call.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

end of the added/removed date, in ISO date format (e.g. "2017-03-25")

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "indie", - "description": "

If the playlist was curated by major labels

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "editorial", - "defaultValue": "true", - "description": "

If true, the response includes playlists curated by the platform (e.g. Spotify Editorial Playlists, Apple Music Editorial Playlists, Deezer Editorial Playlists). API results match the app only when this parameter is set to true. Spotify includes "personalized" playlists when editorial is set to true.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "majorCurator", - "description": "

If true, the response includes playlists curated by major curators (e.g. Filtr Colombia, Digster, Deezer MENA editor). API results match the app only when this parameter is set to true.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "newMusicFriday", - "description": "

A curated selection of Spotify's most anticipated new tracks of the week.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "editorialBrand", - "description": "

If true, includes playlists curated under specific editorial brands or themes by Apple Music's editorial team.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "personalized", - "description": "

If true, the response includes personalized playlists created based on user's listening habits and preferences.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "deezerPartner", - "description": "

If true, the response includes playlists curated by Deezer partners.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "chart", - "description": "

If true, the response includes playlists based on music charts.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "thisIs", - "description": "

If true, the response includes "This Is" playlists curated by Spotify.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "hundredPercent", - "description": "

If true, the response includes "100&" playlists curated by Deezer

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "radio", - "description": "

If true, the response includes playlists from radio stations.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "fullyPersonalized", - "description": "

If true, the response includes fully personalized playlists curated by Spotify based on the user's listening behavior and preferences

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "brand", - "description": "

If true, the response includes playlists related to brands.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "musicBrand", - "description": "

If true, the response includes playlists curated under specific music brands by Apple Music's editorial team.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "nonMusicBrand", - "description": "

If true, the response includes playlists curated under specific non-music brands by Apple Music's editorial team

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "popularIndie", - "description": "

If true, the response includes playlists featuring popular indie tracks.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "audiobook", - "description": "

If true, the response includes audiobook playlists curated by Spotify. These playlists feature audiobook content.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "personalityArtist", - "description": "

If true, the response includes playlists curated under specific personality artists by Apple Music's editorial team

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "sortColumn", - "description": "

Sort by column. See table above for allowed values

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "sortOrderDesc", - "defaultValue": "true", - "description": "

Whether to sort in descending order. Can only be defined when sortColumn is defined

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned (max 100)

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "playlist", - "description": "

playlist details

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.playlist_id", - "description": "

Spotify playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.track_id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.position", - "description": "

Position in the playlist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.added_at", - "description": "

Added date. If status is current then response is sorted by this date.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.name", - "description": "

Playlist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.description", - "description": "

description

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.image_url", - "description": "

Playlist image

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "playlist.sys_last_updated", - "description": "

Stats last updated

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "playlist.last_updated", - "description": "

Playlist last updated

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "playlist.personalized", - "description": "

if Playlist is personalized

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.code2", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.owner_name", - "description": "

Playlist owner (user) name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.owner_id", - "description": "

Chartmetric curator ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.user_id", - "description": "

Playlist owner (user) ID

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "playlist.editorial", - "description": "

If this playlist is curated by an official editorial team. Applies only to Spotify, Apple Music, and Deezer platforms.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "playlist.tags", - "description": "

Tags

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.tags.id", - "description": "

Tags id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.tags.name", - "description": "

Tags name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.followers", - "description": "

Playlist followers

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.num_track", - "description": "

Number track

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.fdiff_week", - "description": "

desc Difference in number of followers compared to previous week, indicating weekly follower growth

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.fdiff_month", - "description": "

desc Difference in number of followers compared to previous month, indicating monthly follower growth

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.catalog", - "description": "

catalog

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "playlist.active_ratio", - "description": "

Active ratio

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "track", - "description": "

Track details

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track.id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "name", - "optional": false, - "field": "track.name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "isrc", - "optional": false, - "field": "track.isrc", - "description": "

Track isrc

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.spotify_album_id", - "description": "

Track Spotify album ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.image_url", - "description": "

Track image url

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track.spotify_popularity", - "description": "

Track's popularity on Spotify

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track.cm_track", - "description": "

Chartmetric track ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_artist_names", - "description": "

Names of Spotify artists

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.track_genre", - "description": "

Genre names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.cm_artist", - "description": "

Artist IDs on Chartmetric

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.artist_names", - "description": "

Artist names on Chartmetric

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.code2s", - "description": "

Artist code2s

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.artist_images", - "description": "

Artist images

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.artist_covers", - "description": "

Artist covers

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_artist_ids", - "description": "

Artist IDs on Spotify

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_track_Ids", - "description": "

Track IDs on Spotify

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_album_ids", - "description": "

Track album IDs on Spotify

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_duration_ms", - "description": "

Durations on Spotify

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_ids", - "description": "

track album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_names", - "description": "

track album names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_upc", - "description": "

track album upc

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_label", - "description": "

track album labels

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.release_dates", - "description": "

release date of track

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"playlist\": {\n \"playlist_id\": \"37i9dQZF1DWTkxQvqMy4WW\",\n \"track_id\": 4445159,\n \"peak_position\": 3,\n \"position\": 65,\n \"cm_track\": 15711334,\n \"fdiff_month\": 4528,\n \"added_at\": \"2023-08-23\",\n \"period\": 62,\n \"followers\": 3337655,\n \"track\": \"What Ifs (feat. Lauren Alaina)\",\n \"name\": \"Chillin' on a Dirt Road\",\n \"playlist_image_url\": \"https://i.scdn.co/image/ab67706f00000003ac122bacaf283ff31b362030\",\n \"code2\": \"US\",\n \"curator_name\": \"Spotify\",\n \"id\": 1635107,\n \"description\": \"Country music to get you back to the basics.\",\n \"image_url\": \"https://i.scdn.co/image/ab67706f00000003ac122bacaf283ff31b362030\",\n \"sys_last_updated\": \"2023-10-24T00:00:00.000Z\",\n \"last_updated\": \"2023-10-24T00:40:14.000Z\",\n \"personalized\": true,\n \"owner_name\": \"Spotify\",\n \"owner_id\": 2,\n \"user_id\": \"spotify\",\n \"editorial\": false,\n \"tags\": [\n {\n \"id\": 462895,\n \"name\": \"Country\"\n },\n {\n \"id\": 462882,\n \"name\": \"Pop\"\n },\n {\n \"id\": 462884,\n \"name\": \"Rock\"\n }\n ],\n \"num_track\": 100,\n \"fdiff_week\": 3631,\n \"catalog\": \"catalogue \",\n \"active_ratio\": 0.83\n },\n \"track\": {\n \"id\": 4445159,\n \"name\": \"What Ifs (feat. Lauren Alaina)\",\n \"isrc\": \"USRN11600859\",\n \"spotify_track_id\": \"7zVCrzzEJU7u24sbJPXA5W\",\n \"spotify_album_id\": \"4hhvgHjPqUOAYkclDkhL2T\",\n \"image_url\": \"https://p.scdn.co/mp3-preview/9a1bc6de88d686138b641e6022a4b30c6e75de74?cid=162b7dc01f3a4a2ca32ed3cec83d1e02\",\n \"spotify_popularity\": 75,\n \"cm_track\": 15711334,\n \"spotify_artist_names\": [\n \"Kane Brown\",\n \"Lauren Alaina\"\n ],\n \"track_genre\": \"contemporary country,country,country dawn,country pop,country road,modern country rock,contemporary country,country road\",\n \"cm_artist\": [\n 5408,\n 3836\n ],\n \"artist_names\": [\n \"Kane Brown\",\n \"Lauren Alaina\"\n ],\n \"code2s\": [\n \"US\",\n \"US\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/ab676161000051747b44d30c0a151c78d3c0bcaa\",\n \"https://i.scdn.co/image/ab6761610000517430be9c1d7921b1229c8b36ad\"\n ],\n \"artist_covers\": [\n \"https://i.scdn.co/image/419ffb32f2f13d71304867b122b26ae2a8cfe4c9\",\n null\n ],\n \"spotify_artist_ids\": [\n \"3oSJ7TBVCWMDMiYjXNiCKE\",\n \"1v3tdpIdBSW14rHUfiEVOv\"\n ],\n \"spotify_track_ids\": [\n \"7zVCrzzEJU7u24sbJPXA5W\"\n ],\n \"spotify_album_ids\": [\n \"4hhvgHjPqUOAYkclDkhL2T\"\n ],\n \"spotify_duration_ms\": 188253,\n \"album_ids\": [\n 1780125\n ],\n \"album_names\": [\n \"Kane Brown\"\n ],\n \"album_upc\": [\n \"886445733945\"\n ],\n \"album_label\": [\n \"RCA Records Label Nashville\"\n ],\n \"release_dates\": [\n \"2016-12-02\"\n ],\n \"composer_name\": \"Kane Brown, Matthew John Mcginn, Jordan Mark Schmidt\",\n \"album\": [\n {\n \"id\": 1780125,\n \"label\": \"RCA Records Label Nashville\",\n \"name\": \"Kane Brown\",\n \"release_date\": \"2016-12-02\",\n \"upc\": \"886445733945\"\n }\n ],\n \"duration_ms\": 188253,\n \"explicit\": false,\n \"score\": 229,\n \"songwriter\": [\n \"Kane Brown\",\n \"Matthew John Mcginn\",\n \"Jordan Mark Schmidt\"\n ],\n \"artists\": [\n {\n \"code2\": \"US\",\n \"id\": 5408,\n \"name\": \"Kane Brown\"\n },\n {\n \"code2\": \"US\",\n \"id\": 3836,\n \"name\": \"Lauren Alaina\"\n }\n ]\n }\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (only newMusicFriday):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"playlist\": {\n \"playlist_id\": \"37i9dQZF1DXbVCaQthfsdD\",\n \"track_id\": 10139587,\n \"peak_position\": 14,\n \"position\": 14,\n \"cm_track\": 21815071,\n \"fdiff_month\": 569,\n \"added_at\": \"2023-06-01\",\n \"removed_at\": \"2023-06-08 23:15:19.632\",\n \"period\": 7,\n \"followers\": 30303,\n \"track\": \"Sunflower - Spider-Man: Into the Spider-Verse\",\n \"name\": \"New Music Friday Maghreb\",\n \"playlist_image_url\": \"https://i.scdn.co/image/ab67706f0000000381409cf3ec4b9c666f664795\",\n \"curator_name\": \"Spotify\",\n \"id\": 1748659,\n \"description\": \"Le meilleur des nouveautés de la semaine! Cover : R3HAB, Issam Alnajjar & Manal\",\n \"image_url\": \"https://i.scdn.co/image/ab67706f0000000381409cf3ec4b9c666f664795\",\n \"sys_last_updated\": \"2023-07-20T00:00:00.000Z\",\n \"last_updated\": \"2023-07-14T11:12:28.000Z\",\n \"personalized\": false,\n \"owner_name\": \"Spotify\",\n \"owner_id\": 2,\n \"user_id\": \"spotify\",\n \"editorial\": false,\n \"tags\": [\n {\n \"id\": 462883,\n \"name\": \"Hip-Hop/Rap\"\n }\n ],\n \"num_track\": 48,\n \"fdiff_week\": 80,\n \"catalog\": \"frontline\",\n \"active_ratio\": 0.979167\n },\n \"track\": {\n \"id\": 10139587,\n \"name\": \"Sunflower - Spider-Man: Into the Spider-Verse\",\n \"isrc\": \"USUM71814888\",\n \"spotify_track_id\": \"3KkXRkHbMCARz0aVfEt68P\",\n \"spotify_album_id\": \"35s58BRTGAEWztPo9WqCIs\",\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b273e2e352d89826aef6dbd5ff8f\",\n \"spotify_popularity\": 91,\n \"cm_track\": 21815071,\n \"spotify_artist_names\": [\n \"Post Malone\",\n \"Swae Lee\"\n ],\n \"track_genre\": \"dfw rap,melodic rap,pop,rap,trap\",\n \"cm_artist\": [\n 135326,\n 426751\n ],\n \"artist_names\": [\n \"Post Malone\",\n \"Swae Lee\"\n ],\n \"code2s\": [\n \"US\",\n \"US\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/ab676161000051746be070445b03e0b63147c2c1\",\n \"https://i.scdn.co/image/ab67616100005174e3b454b6333a895beb4aa564\"\n ],\n \"artist_covers\": [\n null,\n null\n ],\n \"spotify_artist_ids\": [\n \"246dkjvS1zLTtiykXe5h60\",\n \"1zNqQNIdeOUZHb8zbZRFMX\"\n ],\n \"spotify_track_ids\": [\n \"3KkXRkHbMCARz0aVfEt68P\"\n ],\n \"spotify_album_ids\": [\n \"35s58BRTGAEWztPo9WqCIs\"\n ],\n \"spotify_duration_ms\": 158040,\n \"album_ids\": [\n 3366628\n ],\n \"album_names\": [\n \"Sunflower (Spider-Man: Into the Spider-Verse)\"\n ],\n \"album_upc\": [\n \"602577149658\"\n ],\n \"album_label\": [\n \"Universal Records\"\n ],\n \"release_dates\": [\n \"2018-10-18T00:00:00.000Z\"\n ],\n \"composer_name\": null,\n \"album\": [\n {\n \"id\": 3366628,\n \"label\": \"Universal Records\",\n \"name\": \"Sunflower (Spider-Man: Into the Spider-Verse)\",\n \"release_date\": \"2018-10-18\",\n \"upc\": \"602577149658\"\n }\n ],\n \"duration_ms\": 158040,\n \"explicit\": false,\n \"score\": 1542,\n \"artists\": [\n {\n \"code2\": \"US\",\n \"id\": 135326,\n \"name\": \"Post Malone\"\n },\n {\n \"code2\": \"US\",\n \"id\": 426751,\n \"name\": \"Swae Lee\"\n }\n ]\n }\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/15711334/spotify/current/playlists?editorial=true&radio=false&indie=true&limit=1&offset=0&personalized=true&thisIs=false&newMusicFriday=true&fullyPersonalized=false&brand=true&popularIndie=true&audiobook=false&chart=false&majorCurator=true", - "type": "curl" - }, - { - "title": "Example usage (only newMusicFriday):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/21815071/spotify/past/playlists?editorial=false&personalized=false&chart=false&thisIs=false&newMusicFriday=true&radio=false&fullyPersonalized=false&brand=false&majorCurator=false", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/track.docs.ts", - "groupTitle": "Track" - }, - { - "type": "get", - "url": "/api/track/:id/relatedTracks", - "title": "Related Tracks", - "name": "GetRelatedTracks", - "description": "

Get all related tracks, e.g. remixes, live performances, remastered versions ...etc.

", - "version": "1.0.0", - "group": "Track", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric track ID

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

The related track's Chartmetric track ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

The related track's name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

The related track's image URL.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isrc", - "description": "

The related track's ISRC.

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "is_most_popular_cm_track", - "description": "

Is true if this track is the most popular amongst the related track versions.

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "is_erliest_cm_track", - "description": "

Is true if this track is the earliest released amongst the related track versions.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response (Spotify w/ isDomainId):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"id\": 31826159,\n \"name\": \"34+35\",\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b273ba23b8f2fcaa9bc762e37865\",\n \"isrc\": \"USUM72020424\",\n \"is_most_popular_cm_track\": false,\n \"is_erliest_cm_track\": false\n },\n {\n \"id\": 32560557,\n \"name\": \"34+35 (Remix) [feat. Doja Cat & Megan Thee Stallion]\",\n \"image_url\": \"https://is4-ssl.mzstatic.com/image/thumb/Music124/v4/46/37/de/4637de37-b342-a517-50a5-88d772e8cfc9/20UM1IM17686.rgb.jpg/160x160bb.jpeg\",\n \"isrc\": \"USUM72024483\",\n \"is_most_popular_cm_track\": false,\n \"is_erliest_cm_track\": false\n },\n {\n \"id\": 32564030,\n \"name\": \"34+35 (Remix) [feat. Doja Cat & Megan Thee Stallion]\",\n \"image_url\": \"https://is4-ssl.mzstatic.com/image/thumb/Music114/v4/b4/b3/95/b4b39515-3c23-de6f-521e-d5beb3a31d7d/20UM1IM17687.rgb.jpg/160x160bb.jpeg\",\n \"isrc\": \"USUM72024484\",\n \"is_most_popular_cm_track\": true,\n \"is_erliest_cm_track\": true\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/31825757/relatedTracks", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/track.docs.ts", - "groupTitle": "Track" - }, - { - "type": "get", - "url": "/api/track/:id/topVideos", - "title": "Top TikTok Video", - "name": "GetTopTikTokVideo", - "description": "

Get top TikTok videos for a given track.

", - "version": "1.0.0", - "group": "Track", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric track ID

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned (max 100)

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "'likes'", - "'views'", - "'comments'", - "'followers'" - ], - "optional": true, - "field": "type", - "defaultValue": "likes", - "description": "

The video stat type use to sort the entries

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "created_at", - "description": "

The date the video was created.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "username", - "description": "

The username of the video.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "video_id", - "description": "

The video ID of the video.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "thumbnail_url", - "description": "

URL to the thumbnail of this video.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "followers", - "description": "

Number of followers.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_id", - "description": "

Link target id.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "link", - "description": "

The link to the video.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "stats", - "description": "

An Object containing the videos's stats - likes, comments, and views .

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "stats.likes", - "description": "

Number of likes

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "stats.comments", - "description": "

Number of comments

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "stats.views", - "description": "

Number of views

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"created_at\": \"2021-04-23T07:09:53.962Z\",\n \"username\": \"ivanmarsiglia\",\n \"video_id\": \"6953643605160480005\",\n \"thumbnail_url\": \"https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/1d79b9af19d543349c0e225fef89e01d?x-expires=1619157600&x-signature=1BQln7So4JtcDgbdVuJvzFyaDYY%3D\",\n \"followers\": 290700,\n \"cm_id\": null,\n \"link\": \"https://www.tiktok.com/@ivanmarsiglia/video/6953643605160480005\",\n \"stats\": {\n \"likes\": 8400000,\n \"comments\": 82100,\n \"views\": 54100000\n }\n },\n {\n \"created_at\": \"2021-03-31T10:01:11.685Z\",\n \"username\": \"matt.embassytattoo\",\n \"video_id\": \"6945433390917242118\",\n \"thumbnail_url\": \"https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/dc2cbfbc8e5b4058ad5d0acf76d5d7e3?x-expires=1617181200&x-signature=XxWpqrYFQW5ohYrWksC%2BpK5FJIA%3D\",\n \"followers\": 756100,\n \"cm_id\": null,\n \"link\": \"https://www.tiktok.com/@matt.embassytattoo/video/6945433390917242118\",\n \"stats\": {\n \"likes\": 4000000,\n \"comments\": 32800,\n \"views\": 49100000\n }\n },\n {\n \"created_at\": \"2021-02-07T09:07:02.753Z\",\n \"username\": \"itsjojosiwa\",\n \"video_id\": \"6926306512214248709\",\n \"thumbnail_url\": \"https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/0550f0f7fbbf461f8f0028ce18f1513c_1612656731?x-expires=1612681200&x-signature=9mQak7nCrP182siah46Qwg1HqVg%3D\",\n \"followers\": 33800000,\n \"cm_id\": \"5272\",\n \"link\": \"https://www.tiktok.com/@itsjojosiwa/video/6926306512214248709\",\n \"stats\": {\n \"likes\": 3200000,\n \"comments\": 46800,\n \"views\": 27200000\n }\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/22290726/topVideos?type=likes&limit=3", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/track.docs.ts", - "groupTitle": "Track" - }, - { - "type": "get", - "url": "/api/track/youtube/:id/topShorts", - "title": "Top YouTube Shorts", - "name": "GetTopYouTubeShorts", - "description": "

Get top YouTube Shorts for a given track.

", - "version": "1.0.0", - "group": "Track", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric track ID

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned (max 100)

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "'likes'", - "'views'", - "'comments'", - "'created_at'" - ], - "optional": true, - "field": "type", - "defaultValue": "likes", - "description": "

The video stat type use to sort the entries

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "video_id", - "description": "

The YouTube Shorts video ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "created_at", - "description": "

The date the video was created (ISO 8601 format).

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "modified_at", - "description": "

The date the video was last modified (ISO 8601 format).

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "username", - "description": "

The username of the video creator (with @ prefix).

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "video_title", - "description": "

The title of the video.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_id", - "description": "

Chartmetric track ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "sound_id", - "description": "

The YouTube Sound ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "token_parameter", - "description": "

Token parameter for YouTube Shorts.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "thumbnail_url", - "description": "

URL to the thumbnail of this video.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "link", - "description": "

The link to the YouTube Shorts video.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "shorts_track_page", - "description": "

The link to the YouTube Shorts track page.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "views", - "description": "

Number of views.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "likes", - "description": "

Number of likes.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "comments", - "description": "

Number of comments.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "stats", - "description": "

An object containing the video's stats.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "stats.views", - "description": "

Number of views (duplicate of top-level views).

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "stats.likes", - "description": "

Number of likes (duplicate of top-level likes).

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "stats.comments", - "description": "

Number of comments (duplicate of top-level comments).

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "influencercountries", - "description": "

Array of influencer countries (may be empty).

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "youtubevideohistoricalstats", - "description": "

Array of historical stats (may be empty).

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "metadata_for_url", - "description": "

Metadata for the video URL.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "metadata_for_url.video_id", - "description": "

The YouTube Shorts video ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "metadata_for_url.username", - "description": "

The username of the video creator.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "metadata_for_url.views", - "description": "

Number of views.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "metadata_for_url.likes", - "description": "

Number of likes.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "metadata_for_url.comments", - "description": "

Number of comments.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "metadata_for_url.created_at", - "description": "

The date the video was created (YYYY-MM-DD).

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"total\": 6400000,\n \"available_count\": 10,\n \"data\": [\n {\n \"video_id\": \"5VhmJaKV10c\",\n \"created_at\": \"2025-03-04T11:22:15.890Z\",\n \"modified_at\": \"2025-07-15T09:19:53.132Z\",\n \"username\": \"@roses_are_rosie\",\n \"video_title\": \"excited to see what shorts ideas y’all come up with for apt.💗 brought to you by youtube shorts\",\n \"cm_id\": 138071658,\n \"sound_id\": \"DiTd771WumE\",\n \"token_parameter\": \"8gVNCkASMQoLRGlUZDc3MVd1bUUSC0RpVGQ3NzFXdW1FGgtvYzN1WXRQYXd3dyIICLDnAhICCBAaC29jM3VZdFBhd3d3KNO62pW86dPw4gE\",\n \"thumbnail_url\": \"https://i.ytimg.com/vi/5VhmJaKV10c/maxresdefault.jpg\",\n \"link\": \"https://www.youtube.com/shorts/5VhmJaKV10c\",\n \"shorts_track_page\": \"https://www.youtube.com/source/DiTd771WumE/shorts?app=desktop&bp=8gVNCkASMQoLRGlUZDc3MVd1bUUSC0RpVGQ3NzFXdW1FGgtvYzN1WXRQYXd3dyIICLDnAhICCBAaC29jM3VZdFBhd3d3KNO62pW86dPw4gE\",\n \"views\": 247428457,\n \"likes\": 7235195,\n \"comments\": 70705,\n \"stats\": {\n \"views\": 247428457,\n \"likes\": 7235195,\n \"comments\": 70705\n },\n \"influencercountries\": [],\n \"youtubevideohistoricalstats\": [],\n \"metadata_for_url\": {\n \"video_id\": \"5VhmJaKV10c\",\n \"username\": \"@roses_are_rosie\",\n \"views\": 247428457,\n \"likes\": 7235195,\n \"comments\": 70705,\n \"created_at\": \"2025-07-15\"\n }\n },\n ...\n ]\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/youtube/138071658/topShorts?limit=10", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/track.docs.ts", - "groupTitle": "Track" - }, - { - "type": "get", - "url": "/api/track/:id", - "title": "Metadata", - "name": "GetTrackMetadata", - "version": "1.3.0", - "description": "

Get track's metadata given Chartmetric ID.
This data will be cached daily.

Note: This endpoint response may contain undocumented properties that may be removed in the future. If you choose to use them, we recommend having the proper error checking in place in case the fields are removed in the future.

", - "group": "Track", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric track ID

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric track ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isrc", - "description": "

ISRC code

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "duration_ms", - "description": "

duration of track

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "composer_name", - "description": "

Track composer name

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artists", - "description": "

Artists of track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.id", - "description": "

Artist's Chartmetric ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.name", - "description": "

Artist's name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.image_url", - "description": "

Artist's image url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.cover_url", - "description": "

Artist's album cover url

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.code2", - "description": "

Artist's country code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.gender", - "description": "

Artist's gender

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.isni", - "description": "

Artist's ISNI

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.description", - "description": "

Artist's description

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.created_at", - "description": "

When this data was originally inserted

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "artists.hometown_city", - "description": "

Artist's hometown city

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.currently_city", - "description": "

Artist's currently city

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.date_of_birth", - "description": "

Artist's date of birth

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.date_of_death", - "description": "

Artist's date of death

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.modified_at", - "description": "

When this data was last updated

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "artists.is_duplicate", - "description": "

True if this profile is a duplicate profile, recommend looking for the proper ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.label", - "description": "

Artist's label

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.booking_agent", - "description": "

Artist's booking agent

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.record_label", - "description": "

Artist's record label

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.press_contact", - "description": "

Artist's press contact

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.general_manager", - "description": "

Artist's general manager

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artists.band", - "description": "

Artist's band

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "albums", - "description": "

Albums of track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "albums.id", - "description": "

Album's Chartmetric ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "albums.name", - "description": "

Album's name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "albums.upc", - "description": "

Album's UPC

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "albums.release_date", - "description": "

Album's release date

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "albums.label", - "description": "

Album's label

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "albums.image_url", - "description": "

Album's image url

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "albums.popularity", - "description": "

Album's popularity rating

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "tags", - "description": "

Comma separated list of genres for the track

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "moods", - "description": "

Array of moods of track

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "moods.id", - "description": "

Id of mood

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "moods.name", - "description": "

Name of mood

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "activities", - "description": "

Array of activities of track

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "activities.id", - "description": "

Id of activity

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "activities.name", - "description": "

Name of activity

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "songwriters", - "description": "

Comma separated list of songwriters

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "songwriterIds", - "description": "

Comma separated list of songwriter chartmetric ids

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "release_date", - "description": "

Release date of the oldest album

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album_label", - "description": "

Label name of the oldest album

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "explicit", - "description": "

If the track is explicit

" - }, - { - "group": "Success 200", - "type": "object", - "optional": false, - "field": "cm_statistics", - "description": "

Stat of track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_sp_playlists", - "description": "

Number of spotify playlists featuring this track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_sp_editorial_playlists", - "description": "

Number of editorial spotify playlists featuring this track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_am_playlists", - "description": "

Number of Apple Music playlists featuring this track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_am_editorial_playlists", - "description": "

Number of editorial Apple Music playlists featuring this track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_de_playlists", - "description": "

Number of Deezer playlists featuring this track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_de_editorial_playlists", - "description": "

Number of editorial Deezer playlists featuring this track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_az_playlists", - "description": "

Number of Amazon playlists featuring this track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_az_editorial_playlists", - "description": "

Number of editorial Amazon playlists featuring this track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_yt_playlists", - "description": "

Number of YouTube playlists featuring this track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_yt_editorial_playlists", - "description": "

Number of editorial YouTube playlists featuring this track

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.sp_playlist_total_reach", - "description": "

Sum of followers from all Spotify playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.de_playlist_total_reach", - "description": "

Sum of fans from all Deezer playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.yt_playlist_total_reach", - "description": "

Sum of views from all YouTube playlists, indicating total playlists reach

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.cm_track", - "description": "

Chartmetric track ID

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.sp_popularity", - "description": "

Spotify popularity

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.num_tt_videos", - "description": "

num TikTok videos

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.sp_streams", - "description": "

Spotify streams

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.genius_page_views", - "description": "

Genius page views

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.shazam_counts", - "description": "

Shazam counts

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.tiktok_counts", - "description": "

desc

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.tiktok_trending_user", - "description": "

TikTok trending user id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.tiktok_trending_user_likes", - "description": "

TikTok trending user likes

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.tiktok_trending_user_cm_artist", - "description": "

Chartmetric ID of TikTok trending user

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.youtube_views", - "description": "

YouTube views

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.pandora_lifetime_streams", - "description": "

Pandora lifetime streams

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_statistics.pandora_lifetime_stations_added", - "description": "

Pandora lifetime stations added

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.boomplay_comments", - "description": "

Boomplay comments

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.boomplay_favorites", - "description": "

Boomplay favorites

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.boomplay_shares", - "description": "

Boomplay shares

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.line_music_likes", - "description": "

Line Music likes

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.line_music_listen_count", - "description": "

Line Music listens

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "cm_statistics.melon_likes", - "description": "

Melon likes

" - } - ] - }, - "examples": [ - { - "title": "Success-Response: ", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"id\": 15678739,\n \"name\": \"Angels\",\n \"isrc\": \"USRC11700153\",\n \"image_url\": \"https://i.scdn.co/image/5026fddc6c0b69e931ae99130f15d4214f1e7d41\",\n \"duration_ms\": 170680,\n \"composer_name\": \"N/A\",\n \"artists\": [\n {\n \"id\": 209693,\n \"name\": \"Khalid\",\n \"image_url\": \"https://i.scdn.co/image/ab6761610000e5eb012b37d6dec8872b18524f78\",\n \"cover_url\": null,\n \"code2\": \"US\",\n \"gender\": 1,\n \"isni\": null,\n \"description\": \"Just out of high school when ...\",\n \"created_at\": \"2016-11-26T00:00:00\",\n \"hometown_city\": 6985,\n \"current_city\": null,\n \"date_of_birth\": \"1998-02-11\",\n \"date_of_death\": null,\n \"modified_at\": \"2021-07-30T01:19:35.205682\",\n \"is_duplicate\": false,\n \"label\": null,\n \"booking_agent\": null,\n \"record_label\": null,\n \"press_contact\": null,\n \"general_manager\": null,\n \"band\": false\n }\n ],\n \"albums\": [\n {\n \"id\": 13133047,\n \"name\": \"American Teen\",\n \"upc\": \"886446326146\",\n \"release_date\": \"2017-03-03T08:00:00.000Z\",\n \"label\": \"Right Hand Music Group, LLC/RCA Records\",\n \"image_url\": \"https://is5-ssl.mzstatic.com/image/thumb/Music124/v4/5b/54/f9/5b54f992-e3b3-4809-fcc4-21a9faf484da/886446326146.jpg/160x160bb.jpeg\",\n \"popularity\": null\n },\n {\n \"id\": 1086311,\n \"name\": \"American Teen\",\n \"upc\": \"886446309347\",\n \"release_date\": \"2017-04-27T07:00:00.000Z\",\n \"label\": \"Right Hand Music Group, LLC/RCA Records\",\n \"image_url\": \"https://i.scdn.co/image/5026fddc6c0b69e931ae99130f15d4214f1e7d41\",\n \"popularity\": 80\n }\n ],\n \"tags\": \"Music,R&B/Soul\",\n \"songwriters\": null,\n \"songwriterIds\": null,\n \"release_date\": \"2017-03-03T00:00:00.000Z\",\n \"album_label\": \"Right Hand Music Group, LLC/RCA Records\",\n \"explicit\": false,\n \"cm_statistics\": {\n \"num_sp_playlists\": 3675,\n \"num_sp_editorial_playlists\": 4,\n \"num_am_playlists\": 1,\n \"num_am_editorial_playlists\": 0,\n \"num_de_playlists\": 4,\n \"num_de_editorial_playlists\": 4,\n \"num_az_playlists\": 1,\n \"num_az_editorial_playlists\": 1,\n \"num_yt_playlists\": 0,\n \"num_yt_editorial_playlists\": 0,\n \"sp_playlist_total_reach\": \"4008928\",\n \"de_playlist_total_reach\": \"147882\",\n \"yt_playlist_total_reach\": \"0\",\n \"cm_track\": 15678739,\n \"sp_popularity\": 61,\n \"num_tt_videos\": 954,\n \"sp_streams\": \"107823509\",\n \"genius_page_views\": 297466,\n \"shazam_counts\": 141310,\n \"tiktok_counts\": 954,\n \"tiktok_trending_user\": null,\n \"tiktok_trending_user_likes\": null,\n \"tiktok_trending_user_cm_artist\": null,\n \"soundcloud_playback_count\": \"475255\",\n \"youtube_views\": \"6800747\",\n \"pandora_lifetime_streams\": 14535146,\n \"pandora_lifetime_stations_added\": 6746,\n \"boomplay_comments\": null,\n \"boomplay_favorites\": null,\n \"boomplay_shares\": null,\n \"line_music_likes\": null,\n \"line_music_listen_count\": null,\n \"melon_likes\": null\n }\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/15678739", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/track.docs.ts", - "groupTitle": "Track" - }, - { - "type": "get", - "url": "/api/track/:id/milestones", - "title": "Milestone", - "version": "1.0.0", - "name": "GetTrackMilestones", - "description": "

Get Track’s milestone information.

", - "group": "Track", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "100", - "description": "

The number of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Array", - "allowedValues": [ - "1", - "2", - "3", - "4", - "5" - ], - "optional": true, - "field": "stars", - "description": "

Filter base on stars of data from current data. (e.g. [1, 2, 3])

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "daysAgo", - "description": "

Filter base on artists first release based on number of days ago from current date.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "date", - "stars" - ], - "optional": true, - "field": "sortColumn", - "defaultValue": "date", - "description": "

Sort by column.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "sortOrderDesc", - "defaultValue": "true", - "description": "

Whether to sort in descending order.

" - }, - { - "group": "Parameter", - "type": "Array", - "allowedValues": [ - "amazon", - "apple_music", - "beatport", - "chartmetric", - "deezer", - "itunes", - "pandora", - "shazam", - "soundcloud", - "spotify", - "tiktok", - "youtube" - ], - "optional": true, - "field": "platforms", - "defaultValue": "true", - "description": "

Filter base on platforms from current data.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "grouped", - "description": "

Whether group or not.

" - } - ] - } - }, - "success": { - "fields": { - "grouped = false": [ - { - "group": "grouped = false", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

ID of the milestone.

" - }, - { - "group": "grouped = false", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Date of milestone.

" - }, - { - "group": "grouped = false", - "type": "String", - "optional": false, - "field": "summary", - "description": "

Summary of the milestone, matching what can be found on the aritst profile page.

" - }, - { - "group": "grouped = false", - "type": "String", - "optional": false, - "field": "artist_name", - "description": "

Name of artist.

" - }, - { - "group": "grouped = false", - "type": "String", - "optional": false, - "field": "track_name", - "description": "

Name of track.

" - }, - { - "group": "grouped = false", - "type": "String", - "optional": false, - "field": "platform", - "description": "

Platform of milestone.

" - }, - { - "group": "grouped = false", - "type": "String", - "optional": false, - "field": "stars", - "description": "

Stars of milestone.

" - }, - { - "group": "grouped = false", - "type": "Integer", - "optional": false, - "field": "group_size", - "description": "

Size of each group.

" - } - ], - "grouped = true": [ - { - "group": "grouped = true", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

ID of the milestone.

" - }, - { - "group": "grouped = true", - "type": "Date", - "optional": false, - "field": "date", - "description": "

Date of milestone.

" - }, - { - "group": "grouped = true", - "type": "String", - "optional": false, - "field": "summary", - "description": "

Summary of the milestone, matching what can be found on the aritst profile page.

" - }, - { - "group": "grouped = true", - "type": "String", - "optional": false, - "field": "artist_name", - "description": "

Name of artist.

" - }, - { - "group": "grouped = true", - "type": "String", - "optional": false, - "field": "track_name", - "description": "

Name of track.

" - }, - { - "group": "grouped = true", - "type": "String", - "optional": false, - "field": "platform", - "description": "

Platform of milestone.

" - }, - { - "group": "grouped = true", - "type": "String", - "optional": false, - "field": "stars", - "description": "

Stars of milestone.

" - }, - { - "group": "grouped = true", - "type": "Integer", - "optional": false, - "field": "group_size", - "description": "

Size of each group.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response (grouped = false):", - "content": "HTTP/1.1 200 OK\n\n{\n \"obj\": {\n \"insights\": [\n {\n \"id\": 1754976786,\n \"date\": \"2024-04-29\",\n \"summary\": \"Run added to Spotify Top 50 - Vietnam at position 5 with 90.5K followers.\",\n \"artist_name\": \"BTS\",\n \"track_name\": \"Run\",\n \"platform\": \"Spotify\",\n \"stars\": 4\n },\n ...\n ],\n \"count\": 14\n }\n }", - "type": "json" - }, - { - "title": "Success-Response (grouped = true):", - "content": "HTTP/1.1 200 OK\n\n{\n \"obj\": {\n \"insights\": [\n {\n \"id\": 1754976786,\n \"date\": \"2024-04-29\",\n \"summary\": \"Run added to Spotify Top 50 - Vietnam at position 5 with 90.5K followers.\",\n \"artist_name\": \"BTS\",\n \"track_name\": \"Run\",\n \"platform\": \"Spotify\",\n \"stars\": 4,\n \"group_size\": 1\n\n },\n ...\n ],\n \"count\": 10\n }\n }", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage (grouped = false):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/83623687/milestones?platforms[]=spotify&stars[]=4", - "type": "curl" - }, - { - "title": "Example usage (grouped = true):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.co/api/track/83623687/milestones?platforms[]=spotify&stars[]=4&grouped=true", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/track.docs.ts", - "groupTitle": "Track" - }, - { - "type": "get", - "url": "/api/track/:id/:platform/stats/:mode", - "title": "Stats", - "name": "GetTrackPlatformStats", - "description": "

Returns various statistics for a track.

", - "group": "Track", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric track ID when isDomainId=false, platform ID when isDomainId=true

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "chartmetric", - "spotify", - "youtube", - "shazam", - "tiktok", - "genius", - "soundcloud", - "line", - "melon", - "radio" - ], - "optional": false, - "field": "platform", - "description": "

Streaming platform to retrieve data for

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "highest-playcounts", - "most-history" - ], - "optional": false, - "field": "mode", - "description": "

Defines the algorithm used to select tracks when more than one option is available.
mode=highest-playcounts will select the track with the highest average stat in the last 3 months.
mode=most-history will select the track with the longest historical data.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "since", - "defaultValue": "180 days ago", - "description": "

Start of date range in ISO date format (e.g. 2017-03-25)

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

End of date range in ISO format (e.g. 2017-03-25)

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "latest", - "description": "

If set to true, returns latest data point available regardless of date

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "interpolated", - "defaultValue": "false", - "description": "

If true, returns interpolated data for missing dates

" - }, - { - "group": "Parameter", - "type": "String", - "optional": true, - "field": "type", - "description": "

Specifies the type of statistic to return (applicable for platform=spotify, platform=chartmetric, or platform=tiktok)

Allowed values when platform=spotify:

  • popularity (default) — the Spotify popularity value for this track
  • streams — the number of streams on Spotify for this track

Allowed values when platform=chartmetric:

  • score (default) — the Chartmetric score for this track (rounded; multiple tracks may have the same score)
  • rank — the Chartmetric rank for this track (unique; only one track is assigned to a rank value)

Allowed values when platform=tiktok:

  • posts (default) — the number of TikTok posts using this track
  • views — the total views on TikTok videos using this track
  • comments — the total comments on TikTok videos using this track
  • sound_posts — the number of posts using this sound
  • sound_views — the total views on videos using this sound
  • sound_likes — the total likes on videos using this sound
  • sound_comments — the total comments on videos using this sound
" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "isDomainId", - "defaultValue": "false", - "description": "

If true, the id passed in the request parameter will be considered the platform ID. If false the :id will be considered Chartmetric's track ID.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "domain", - "description": "

Domain.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track_domain_id", - "description": "

Track domain ID.

" - }, - { - "group": "Success 200", - "type": "Object[]", - "optional": false, - "field": "data", - "description": "

Array of stat objects for each date.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.value", - "description": "

Statistic from given service. Either popularity or streams for Spotify, views for YouTube, play count for Shazam and SoundCloud, posts count for TikTok, page views for Genius, and track score or rank for Chartmetric.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.timestp", - "description": "

The time this statistic was achieved.

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.daily_diff", - "description": "

The difference between current and previous statistic (not applicable for Spotify). This field is called diff if interpolated=false

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": true, - "field": "data.interpolated", - "defaultValue": "false", - "description": "

If true, the value field did not come directly from the service, but is rather an interpolated value to create a smooth curve (not applicable for Spotify)

" - } - ] - }, - "examples": [ - { - "title": "Success-Response (Spotify, highest-playcounts):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"domain\": \"spotify\",\n \"track_domain_id\": \"55n9yjI6qqXh5F2mYvUc2y\",\n \"type\": \"streams\",\n \"data\": [\n {\n \"value\": 80,\n \"timestp\": \"2022-01-01T00:00:00.000Z\"\n },\n {\n \"value\": 80,\n \"timestp\": \"2022-01-02T00:00:00.000Z\"\n },\n ...\n ]\n }\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (Spotify, most-history):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"domain\": \"spotify\",\n \"track_domain_id\": \"2y5aJvzXhHPA94U5GFAcXe\",\n \"type\": \"streams\",\n \"data\": [\n {\n \"value\": 68,\n \"timestp\": \"2022-01-01T00:00:00.000Z\"\n },\n {\n \"value\": 69,\n \"timestp\": \"2022-01-02T00:00:00.000Z\"\n },\n ...\n ]\n }\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (TikTok):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"domain\": \"tiktok\",\n \"track_domain_id\": \"Wannabe-5000000001458394355\",\n \"data\": [\n {\n \"value\": 10300,\n \"timestp\": \"2021-10-17T00:00:00.000Z\",\n \"diff\": null\n },\n {\n \"value\": 10300,\n \"timestp\": \"2021-11-04T00:00:00.000Z\",\n \"diff\": 0\n },\n ...\n ]\n }\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (Spotify w/ isDomainId):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"domain\": \"spotify\",\n \"track_domain_id\": \"5hTpBe8h35rJ67eAWHQsJx\",\n \"type\": \"popularity\",\n \"data\": [\n {\n \"value\": 658888613,\n \"timestp\": \"2022-07-11T00:00:00.000Z\"\n }\n ]\n }\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (Chartmetric):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"domain\": \"chartmetric\",\n \"track_domain_id\": \"15611361\",\n \"type\": \"score\",\n \"data\": [\n {\n \"value\": null,\n \"timestp\": \"2022-11-30\",\n \"diff\": -12\n },\n {\n \"value\": 1279,\n \"timestp\": \"2022-12-01\",\n \"diff\": -2\n },\n ...\n ]\n }\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (Line, listen count):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"domain\": \"line\",\n \"type\": \"listen_count\",\n \"data\": [\n {\n \"value\": 19053119,\n \"timestp\": \"2025-05-10T07:00:00.000Z\",\n \"diff\": null\n },\n {\n \"value\": 19131853,\n \"timestp\": \"2025-05-11T07:00:00.000Z\",\n \"diff\": 78734\n },\n {\n \"value\": 19150951,\n \"timestp\": \"2025-05-12T07:00:00.000Z\",\n \"diff\": 19098\n }\n ]\n }\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (Melon, likes):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"domain\": \"melon\",\n \"type\": \"likes_melon\",\n \"data\": [\n {\n \"value\": 211674,\n \"timestp\": \"2025-05-10T07:00:00.000Z\",\n \"diff\": null\n },\n {\n \"value\": 211751,\n \"timestp\": \"2025-05-11T07:00:00.000Z\",\n \"diff\": 77\n },\n {\n \"value\": 211832,\n \"timestp\": \"2025-05-12T07:00:00.000Z\",\n \"diff\": 81\n }\n ]\n }\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (Radio Airplay, total spins):", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"domain\": \"radio\",\n \"type\": \"total_spins\",\n \"data\": [\n {\n \"value\": 586927,\n \"timestp\": \"2025-05-10T07:00:00.000Z\",\n \"diff\": null\n },\n {\n \"value\": 589249,\n \"timestp\": \"2025-05-11T07:00:00.000Z\",\n \"diff\": 2322\n },\n {\n \"value\": 591346,\n \"timestp\": \"2025-05-12T07:00:00.000Z\",\n \"diff\": 2097\n }\n ]\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage (Spotify, highest-playcounts):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/15611361/spotify/stats/highest-playcounts?since=2022-01-01", - "type": "curl" - }, - { - "title": "Example usage (Spotify, most-history):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/15611361/spotify/stats/most-history?since=2022-01-01", - "type": "curl" - }, - { - "title": "Example usage (TikTok):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/14424995/tiktok/stats/highest-playcounts?since=2021-10-01&until=2022-01-01", - "type": "curl" - }, - { - "title": "Example usage (Spotify w/ isDomainId):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/5hTpBe8h35rJ67eAWHQsJx/spotify/stats/most-history?isDomainId=true&latest=true&type=streams", - "type": "curl" - }, - { - "title": "Example usage (Line, listen count):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/138071658/line/stats/most-history?latest=false&since=2025-05-10&until=2025-05-12&type=listen_count", - "type": "curl" - }, - { - "title": "Example usage (Line, artist likes):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/138071658/line/stats/most-history?latest=false&since=2025-05-10&until=2025-05-12&type=artist_likes", - "type": "curl" - }, - { - "title": "Example usage (Melon, likes):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/138071658/melon/stats/most-history?latest=false&since=2025-05-10&until=2025-05-12&type=likes_melon", - "type": "curl" - }, - { - "title": "Example usage (Airplay, total spins):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/138071658/radio/stats/most-history?latest=false&since=2025-05-10&until=2025-05-12&type=total_spins", - "type": "curl" - } - ], - "version": "0.0.0", - "filename": "src/Routers/apiRouter/docs/track.docs.ts", - "groupTitle": "Track" - }, - { - "type": "get", - "url": "/api/track/:id/:platform/playlists/snapshot", - "title": "Playlist Snapshot", - "name": "GetTrackPlaylistsSnapshot", - "version": "1.0.0", - "group": "Track", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Chartmetric track ID

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify", - "applemusic", - "deezer", - "amazon", - "youtube" - ], - "optional": false, - "field": "platform", - "description": "

Streaming platform type

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "date", - "description": "

Datetime

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "limit", - "defaultValue": "10", - "description": "

The number of entries to be returned

" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

starting point

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist_id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track_id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "added_at", - "description": "

Added date

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "removed_at", - "description": "

Removed date

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "playlist", - "description": "

playlist

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.id", - "description": "

Chartmetric playlist ID

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.name", - "description": "

Playlist name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.playlist_id", - "description": "

Playlist id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.description", - "description": "

description

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.image_url", - "description": "

Playlist image

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "playlist.sys_last_updated", - "description": "

System Last update

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "playlist.last_updated", - "description": "

Last update

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "playlist.personalized", - "description": "

Personalized playlist

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.code2", - "description": "

Country code

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.owner_name", - "description": "

Curator name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.owner_id", - "description": "

Chartmetric curator id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.user_id", - "description": "

user id

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "playlist.editorial", - "description": "

If this playlist is curated by an official editorial team. Applies only to Spotify, Apple Music, and Deezer platforms.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "playlist.tags", - "description": "

tags

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.tags.id", - "description": "

tags id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.tags.name", - "description": "

tags name

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.followers", - "description": "

Playlist followers

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.num_track", - "description": "

Number of track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.fdiff_week", - "description": "

Difference in number of followers compared to previous week, indicating weekly follower growth

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "playlist.fdiff_month", - "description": "

Difference in number of followers compared to previous month, indicating monthly follower growth

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "playlist.catalog", - "description": "

catalog

" - }, - { - "group": "Success 200", - "type": "Float", - "optional": false, - "field": "playlist.active_ratio", - "description": "

Active ratio

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "track", - "description": "

track

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track.id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "name", - "optional": false, - "field": "track.name", - "description": "

track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.isrc", - "description": "

track isrc

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.spotify_album_id", - "description": "

track's Spotify album id

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.image_url", - "description": "

track's image url

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track.spotify_popularity", - "description": "

track's popularity on Spotify

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track.cm_track", - "description": "

Chartmetric track ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_artist_names", - "description": "

List of Spotify artist names

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "track.track_genre", - "description": "

track genre

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.cm_artist", - "description": "

Chartmetric artist IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.artist_names", - "description": "

Artist names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.code2s", - "description": "

Country codes

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.artist_images", - "description": "

Artist images

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.artist_covers", - "description": "

Artist covers

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_artist_ids", - "description": "

Spotify artist IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_track_ids", - "description": "

Spotify track IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.spotify_album_ids", - "description": "

Spotify album IDs

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "track.spotify_duration_ms", - "description": "

Spotify duration

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_ids", - "description": "

Album ids

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_names", - "description": "

Album names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_upc", - "description": "

Album upc

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.album_label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "track.release_dates", - "description": "

Release dates

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"playlist_id\": 688460,\n \"track_id\": 9874724,\n \"added_at\": \"2018-09-27T00:00:00.000Z\",\n \"removed_at\": \"2020-02-09T08:11:39.008Z\",\n \"playlist\": {\n \"id\": 688460,\n \"name\": \"NEW POP HITS 2014-HEUTE\",\n \"playlist_id\": \"6JjPdcrvR4l6ZAoga2EFqh\",\n \"description\": \"NEW POP HITS 2014-HEUTE (Daily Updated Music) by DjCK\",\n \"image_url\": \"https://i.scdn.co/image/ab67706c0000bebb198fdd7caeedd2d8517f35c7\",\n \"sys_last_updated\": \"2020-12-21T20:40:25.686Z\",\n \"last_updated\": \"2020-12-21T20:40:17.114Z\",\n \"personalized\": false,\n \"code2\": null,\n \"owner_name\": \"Christian Kreitz\",\n \"owner_id\": 11781,\n \"user_id\": \"1156916120\",\n \"editorial\": false,\n \"tags\": [\n {\n \"id\": 188,\n \"name\": \"pop\"\n },\n {\n \"id\": 8893,\n \"name\": \"dance pop\"\n }\n ],\n \"followers\": 3,\n \"num_track\": \"3056\",\n \"fdiff_week\": 0,\n \"fdiff_month\": 0,\n \"catalog\": \"catalogue \",\n \"active_ratio\": 0.032068\n },\n \"track\": {\n \"id\": 9874724,\n \"name\": \"Taki Taki (with Selena Gomez, Ozuna & Cardi B)\",\n \"isrc\": \"USUG11801723\",\n \"spotify_album_id\": \"0ZHyvPke0EcVt6rYA8Ins3\",\n \"image_url\": \"https://i.scdn.co/image/cca02c139eb4270d5e396086329c97c782c7b478\",\n \"spotify_popularity\": 80,\n \"cm_track\": 20614006,\n \"spotify_artist_names\": [\n \"DJ Snake\",\n \"Selena Gomez\",\n \"Ozuna\",\n \"Cardi B\"\n ],\n \"track_genre\": \"dance pop,pop,post-teen pop,electronic trap,puerto rican pop,reggaeton,trap latino,pop,rap\",\n \"cm_artist\": [\n 4310,\n 3168,\n 113381,\n 200408\n ],\n \"artist_names\": [\n \"DJ Snake\",\n \"Selena Gomez\",\n \"Ozuna\",\n \"Cardi B\"\n ],\n \"code2s\": [\n \"FR\",\n \"US\",\n \"PR\",\n \"US\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/3b232935a2d2dc1fcc3bfd8db2d6be58292b2317\",\n \"https://i.scdn.co/image/61fc36467d4945514cdf052ed49286aa8ea3b4fc\",\n \"https://i.scdn.co/image/2ab3f8cdb78a3cde32002e32f42357e1a3a3f468\",\n \"https://i.scdn.co/image/e9c7d7e446f19d445898eb362417542b134863af\"\n ],\n \"artist_covers\": [\n null,\n null,\n null,\n null\n ],\n \"spotify_artist_ids\": [\n \"540vIaP2JwjQb9dm3aArA4\",\n \"0C8ZW7ezQVs4URX5aX7Kqx\",\n \"1i8SpTcr7yvPOmcqrbnVXY\",\n \"4kYSro6naA4h99UJvo89HB\"\n ],\n \"spotify_track_ids\": [\n \"4w8niZpiMy6qz1mntFA5uM\"\n ],\n \"spotify_album_ids\": [\n \"0ZHyvPke0EcVt6rYA8Ins3\"\n ],\n \"spotify_duration_ms\": 212500,\n \"album_ids\": [\n 2812303\n ],\n \"album_names\": [\n \"Taki Taki (with Selena Gomez, Ozuna & Cardi B)\"\n ],\n \"album_upc\": [\n \"602577004803\"\n ],\n \"album_label\": [\n \"DJ Snake Music Productions Limited, under exclusive license to Geffen Records\"\n ],\n \"release_dates\": [\n \"2018-09-28T00:00:00.000Z\"\n ]\n }\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/20614006/spotify/playlists/snapshot?date=2018-10-20&offset=0&limit=10", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/track.docs.ts", - "groupTitle": "Track" - }, - { - "type": "get", - "url": "/api/track/list/filter", - "title": "Get Tracks (with filters)", - "version": "1.3.0", - "name": "TrackList", - "description": "

Retrieve a list of tracks filtering by different parameters.

Track stats

For every track, the following stats are available:

  • score.
  • release_date (part of the album object).
  • airplay_streams.
  • amazon_playlist_count.
  • deezer_playlist_count.
  • itunes_playlist_count.
  • shazam_count.
  • siriusxm_streams.
  • soundcloud_plays.
  • spotify_playlist_count.
  • spotify_playlist_total_reach.
  • spotify_plays.
  • spotify_popularity.
  • spotify_ed_playlist_count.
  • spotify_ed_playlist_count.
  • tidal_popularity.
  • tiktok_posts.
  • tiktok_top_videos_likes.
  • tiktok_top_videos_views.
  • youtube_likes.
  • youtube_playlist_count.
  • youtube_views.

Some stats are associated with different periods in the form <period>.<stat>:

Period Explanation
latest The latest available value
weekly_diff Change in the last 7 days
monthly_diff Change in the last 28 days
weekly_diff_percent Change in the last 7 days in percentage
monthly_diff_percent Change in the last 28 days in percentage

This association denotes the period of a given stat. For example, the stat weekly_diff.youtube_playlist_count refers to the change in YouTubue playlists counts during the last 7 days for a given track.

On the other hand, some stats don't have historic values, and they are NOT associated with any period:

Stat Available periods
score N/A
release_date N/A
airplay_streams latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
amazon_playlist_count latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
deezer_playlist_count latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
itunes_playlist_count latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
shazam_count latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
siriusxm_streams latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
soundcloud_plays latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
spotify_playlist_count latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
spotify_playlist_total_reach latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
spotify_plays latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
spotify_popularity latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
spotify_ed_playlist_count latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
spotify_ed_playlist_count latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
tidal_popularity latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
tiktok_posts latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
tiktok_top_videos_likes latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
tiktok_top_videos_views latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
youtube_likes latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
youtube_playlist_count latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
youtube_views latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent
rank latest, weekly_diff, monthly_diff, monthly_diff_percent, weekly_diff_percent

Sorting

Sorting is done by passing any valid combination of <period>.<stat> from the table above to the parameter sortColumn. For example, given the stat airplay_streams, it's possible to sort by any of latest.airplay_streams, weekly_diff.airplay_streams, and monthly_diff.airplay_streams.

Since score and release_date are not associated with any period, they are passed just as sortColumn=score and sortColumn=release_date.

Applying range filters

It's possible to apply range filters to all of the stats that were presented above. First, the range_period parameter is used to set the stat period. For example, setting range_period=weekly_diff, will filter the specified stats by their weekly changes. The stats that are not associated with any period ignore the value of range_period.

For each stat, there is a corresponding min_ and max_ parameter to set the lower and upper boundaries used to filter, respectively. For example, we have both min_tiktok_posts and max_tiktok_posts parameters that are used to set the tiktok_posts lower and upper boundaries of the given period. As a second example, setting range_period=latest and max_tiktok_posts=100 will filter tracks that currently (using the latest available stats) have less than 100 TikTok posts.

Applying track cluster types filters

To use the version_only, version_and, version_or, and version_not parameters, make sure to set originalsOnly=false. Otherwise, these four version parameters will not work. You can refer to the Artist - Tracks section for usage examples.

To summarize:

  1. Set the period to apply the range filter. E.g.: range_period=weekly_diff to apply filters considering 7-day changes in track stats.
  2. Set the min_<stat> and max_<stat> parameters to be applied to the filter. E.g.: min_spotify_popularity=1 and max_spotify_popularity=10 to filter the tracks that have changed between [1, 10] places in their Spotify popularity in the last week.
  3. It's possible to set min_score, max_score, min_release_date, and max_release_date regardless of the range_period value.

Note that it's possible to set negative values to these filters. E.g.: min_spotify_popularity=-2 to filter tracks that have a spotify_popularity of more than -2 for the given range_period. It's possible to set as many range filters as desired, but note that if too many filters are applied it's likely no tracks will match.

", - "group": "Track", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Number", - "size": "1-100", - "optional": true, - "field": "limit", - "defaultValue": "50", - "description": "

The number of tracks to return. Maximum: 100.

" - }, - { - "group": "Parameter", - "type": "Number", - "size": "0-10,000", - "optional": true, - "field": "offset", - "defaultValue": "0", - "description": "

The offset of entries to be returned. Note that offset + limit has to be smaller than 10,000.

" - }, - { - "group": "Parameter", - "type": "Boolean", - "optional": true, - "field": "sortOrderDesc", - "defaultValue": "true", - "description": "

Whether to sort in descending order.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "score", - "release_date", - "latest.airplay_streams", - "latest.amazon_playlist_count", - "latest.deezer_playlist_count", - "latest.itunes_playlist_count", - "latest.shazam_count", - "latest.siriusxm_streams", - "latest.soundcloud_plays", - "latest.spotify_playlist_count", - "latest.spotify_playlist_total_reach", - "latest.spotify_plays", - "latest.spotify_popularity", - "latest.tiktok_posts", - "latest.tiktok_top_videos_likes", - "latest.tiktok_top_videos_views", - "latest.youtube_likes", - "latest.youtube_playlist_count", - "latest.youtube_views", - "monthly_diff.airplay_streams", - "monthly_diff.amazon_playlist_count", - "monthly_diff.deezer_playlist_count", - "monthly_diff.itunes_playlist_count", - "monthly_diff.shazam_count", - "monthly_diff.siriusxm_streams", - "monthly_diff.soundcloud_plays", - "monthly_diff.spotify_playlist_count", - "monthly_diff.spotify_playlist_total_reach", - "monthly_diff.spotify_plays", - "monthly_diff.spotify_popularity", - "monthly_diff.tiktok_posts", - "monthly_diff.tiktok_top_videos_likes", - "monthly_diff.tiktok_top_videos_views", - "monthly_diff.youtube_likes", - "monthly_diff.youtube_playlist_count", - "monthly_diff.youtube_views", - "weekly_diff.airplay_streams", - "weekly_diff.amazon_playlist_count", - "weekly_diff.deezer_playlist_count", - "weekly_diff.itunes_playlist_count", - "weekly_diff.shazam_count", - "weekly_diff.siriusxm_streams", - "weekly_diff.soundcloud_plays", - "weekly_diff.spotify_playlist_count", - "weekly_diff.spotify_playlist_total_reach", - "weekly_diff.spotify_plays", - "weekly_diff.spotify_popularity", - "weekly_diff.tiktok_posts", - "weekly_diff.tiktok_top_videos_likes", - "weekly_diff.tiktok_top_videos_views", - "weekly_diff.youtube_likes", - "weekly_diff.youtube_playlist_count", - "weekly_diff.youtube_views", - "monthly_diff_percent.rank", - "monthly_diff_percent.airplay_streams", - "monthly_diff_percent.amazon_playlist_count", - "monthly_diff_percent.deezer_playlist_count", - "monthly_diff_percent.itunes_playlist_count", - "monthly_diff_percent.shazam_count", - "monthly_diff_percent.siriusxm_streams", - "monthly_diff_percent.soundcloud_plays", - "monthly_diff_percent.spotify_playlist_count", - "monthly_diff_percent.spotify_playlist_total_reach", - "monthly_diff_percent.spotify_plays", - "monthly_diff_percent.spotify_popularity", - "monthly_diff_percent.spotify_ed_playlist_count", - "monthly_diff_percent.spotify_ed_playlist_total_reach", - "monthly_diff_percent.tidal_popularity", - "monthly_diff_percent.tiktok_posts", - "monthly_diff_percent.tiktok_top_videos_likes", - "monthly_diff_percent.tiktok_top_videos_views", - "monthly_diff_percent.youtube_likes", - "monthly_diff_percent.youtube_playlist_count", - "monthly_diff_percent.youtube_views", - "weekly_diff_percent.rank", - "weekly_diff_percent.airplay_streams", - "weekly_diff_percent.amazon_playlist_count", - "weekly_diff_percent.deezer_playlist_count", - "weekly_diff_percent.itunes_playlist_count", - "weekly_diff_percent.shazam_count", - "weekly_diff_percent.siriusxm_streams", - "weekly_diff_percent.soundcloud_plays", - "weekly_diff_percent.spotify_playlist_count", - "weekly_diff_percent.spotify_playlist_total_reach", - "weekly_diff_percent.spotify_plays", - "weekly_diff_percent.spotify_popularity", - "weekly_diff_percent.spotify_ed_playlist_count", - "weekly_diff_percent.spotify_ed_playlist_total_reach", - "weekly_diff_percent.tidal_popularity", - "weekly_diff_percent.tiktok_posts", - "weekly_diff_percent.tiktok_top_videos_likes", - "weekly_diff_percent.tiktok_top_videos_views", - "weekly_diff_percent.youtube_likes", - "weekly_diff_percent.youtube_playlist_count", - "weekly_diff_percent.youtube_views" - ], - "optional": true, - "field": "sortColumn", - "defaultValue": "score", - "description": "

Column to sort results by.

" - }, - { - "group": "Parameter", - "type": "Number[]", - "optional": true, - "field": "genres", - "description": "

Chartmetric genre IDs to filter tracks by.

" - }, - { - "group": "Parameter", - "type": "Number[]", - "optional": true, - "field": "artists", - "description": "

Chartmetric artist IDs to filter tracks by.

" - }, - { - "group": "Parameter", - "type": "Number[]", - "optional": true, - "field": "shortlistIds", - "description": "

Array of shortlist IDs to filter by.

" - }, - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "latest", - "monthly_diff", - "weekly_diff", - "monthly_diff_percent", - "weekly_diff_percent" - ], - "optional": true, - "field": "range_period", - "defaultValue": "latest", - "description": "

The stat's period used to apply range filters.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_score", - "description": "

Value used to filter tracks by maximum score.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_score", - "description": "

Value used to filter tracks by minimum score.

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "max_release_date", - "description": "

Value used to filter tracks by maximum release_date. Format YYYY-MM-DD.

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "min_release_date", - "description": "

Value used to filter tracks by minimum release_date. Format YYYY-MM-DD.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_airplay_streams", - "description": "

Value used to filter tracks by maximum airplay_streams. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_airplay_streams", - "description": "

Value used to filter tracks by minimum airplay_streams. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_amazon_playlist_count", - "description": "

Value used to filter tracks by maximum amazon_playlist_count. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_amazon_playlist_count", - "description": "

Value used to filter tracks by minimum amazon_playlist_count. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_deezer_playlist_count", - "description": "

Value used to filter tracks by maximum deezer_playlist_count. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_deezer_playlist_count", - "description": "

Value used to filter tracks by minimum deezer_playlist_count. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_itunes_playlist_count", - "description": "

Value used to filter tracks by maximum itunes_playlist_count. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_itunes_playlist_count", - "description": "

Value used to filter tracks by minimum itunes_playlist_count. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_shazam_count", - "description": "

Value used to filter tracks by maximum shazam_count. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_shazam_count", - "description": "

Value used to filter tracks by minimum shazam_count. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_siriusxm_streams", - "description": "

Value used to filter tracks by maximum siriusxm_streams. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_siriusxm_streams", - "description": "

Value used to filter tracks by minimum siriusxm_streams. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_soundcloud_plays", - "description": "

Value used to filter tracks by maximum soundcloud_plays. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_soundcloud_plays", - "description": "

Value used to filter tracks by minimum soundcloud_plays. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_spotify_playlist_count", - "description": "

Value used to filter tracks by maximum spotify_playlist_count. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_spotify_playlist_count", - "description": "

Value used to filter tracks by minimum spotify_playlist_count. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_spotify_playlist_total_reach", - "description": "

Value used to filter tracks by maximum spotify_playlist_total_reach. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_spotify_playlist_total_reach", - "description": "

Value used to filter tracks by minimum spotify_playlist_total_reach. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_spotify_plays", - "description": "

Value used to filter tracks by maximum spotify_plays. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_spotify_plays", - "description": "

Value used to filter tracks by minimum spotify_plays. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_spotify_popularity", - "description": "

Value used to filter tracks by maximum spotify_popularity. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_spotify_popularity", - "description": "

Value used to filter tracks by minimum spotify_popularity. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_spotify_ed_playlist_count", - "description": "

Value used to filter tracks by maximum spotify_ed_playlist_count. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_spotify_ed_playlist_count", - "description": "

Value used to filter tracks by minimum spotify_ed_playlist_count. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_spotify_ed_playlist_total_reach", - "description": "

Value used to filter tracks by maximum spotify_ed_playlist_total_reach. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_spotify_ed_playlist_total_reach", - "description": "

Value used to filter tracks by minimum spotify_ed_playlist_total_reach. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_tidal_popularity", - "description": "

Value used to filter tracks by maximum tidal_popularity. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_tidal_popularity", - "description": "

Value used to filter tracks by minimum tidal_popularity. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_tiktok_posts", - "description": "

Value used to filter tracks by maximum tiktok_posts. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_tiktok_posts", - "description": "

Value used to filter tracks by minimum tiktok_posts. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_tiktok_top_videos_likes", - "description": "

Value used to filter tracks by maximum tiktok_top_videos_likes. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_tiktok_top_videos_likes", - "description": "

Value used to filter tracks by minimum tiktok_top_videos_likes. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_tiktok_top_videos_views", - "description": "

Value used to filter tracks by maximum tiktok_top_videos_views. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_tiktok_top_videos_views", - "description": "

Value used to filter tracks by minimum tiktok_top_videos_views. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_youtube_likes", - "description": "

Value used to filter tracks by maximum youtube_likes. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_youtube_likes", - "description": "

Value used to filter tracks by minimum youtube_likes. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_youtube_playlist_count", - "description": "

Value used to filter tracks by maximum youtube_playlist_count. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_youtube_playlist_count", - "description": "

Value used to filter tracks by minimum youtube_playlist_count. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "max_youtube_views", - "description": "

Value used to filter tracks by maximum youtube_views. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "Number", - "optional": true, - "field": "min_youtube_views", - "description": "

Value used to filter tracks by minimum youtube_views. The range_period parameter is applied to this stat.

" - }, - { - "group": "Parameter", - "type": "String[]", - "optional": true, - "field": "version_only", - "description": "

Types of version: Filter tracks where only the specified types are true and others false.
Can't be used with version_and, version_or, and version_not.

" - }, - { - "group": "Parameter", - "type": "String[]", - "optional": true, - "field": "version_and", - "description": "

Types of version: Filter tracks that contain all of the specified types.
Can’t be used with version_or.

" - }, - { - "group": "Parameter", - "type": "String[]", - "optional": true, - "field": "version_or", - "description": "

Types of version: Filter tracks that contain at least one of the specified types.
Can’t be used with version_and.

" - }, - { - "group": "Parameter", - "type": "String[]", - "optional": true, - "field": "version_not", - "description": "

Types of version: Filter tracks that do not contain any of the specified types.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "cm_track", - "description": "

Chartmetric track ID.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "album", - "description": "

Track album metadata.

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "album.release_date", - "description": "

Album release date in YYYY-MM-DD format. Also considered to be the track's release date.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.name", - "description": "

Album title.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.upc", - "description": "

Album UPC.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "album.id", - "description": "

Chartmetric album ID.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "album.label", - "description": "

Album label.

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "artist", - "description": "

Array of artists related to the track.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "artist.name", - "description": "

Artist's name.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "artist.id", - "description": "

Chartmetric artist ID.

" - }, - { - "group": "Success 200", - "type": "Boolean", - "optional": false, - "field": "explicit", - "description": "

Whether the track is explicit.

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "genre", - "description": "

Track genres.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "image_url", - "description": "

Track image URL.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isrc", - "description": "

Track ISRC.

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "name", - "description": "

Track name.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "score", - "description": "

Charmetric's track score.

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "songwriter", - "description": "

Track's songwriters names.

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "moods", - "description": "

Track's moods.

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "cm_track_cluster", - "description": "

Chartmetric's track cluster (null if standalone track)

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "cm_track_cluster.cluster_id", - "description": "

Cluster ID of the original track

" - }, - { - "group": "Success 200", - "type": "String[]", - "optional": false, - "field": "cm_track_cluster.track_types", - "description": "

Types of tracks in the cluster (e.g., original, remix, cover, etc.)

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "latest", - "description": "

Object with most recent track stats.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.airplay_streams", - "description": "

Airplay streams.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.amazon_playlist_count", - "description": "

Number of Amazon playlists that include this track.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.deezer_playlist_count", - "description": "

Number of Deezer playlists that include this track.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.itunes_playlist_count", - "description": "

Number of iTunes playlists that include this track.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.shazam_count", - "description": "

Number of Shazam plays.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.siriusxm_streams", - "description": "

Number of SiriusXM plays.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.soundcloud_plays", - "description": "

Number of SoundCloud plays.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.spotify_playlist_count", - "description": "

Number of Spotify playlists that include this track.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.spotify_playlist_total_reach", - "description": "

Number of Spotify playlists total reach.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.spotify_plays", - "description": "

Number of Spotify plays.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.spotify_popularity", - "description": "

Track's Spotify popularity.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.spotify_ed_playlist_count", - "description": "

Number of Spotify editorial playlists that include this track.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.spotify_ed_playlist_total_reach", - "description": "

Number of Spotify editorial playlists total reach.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.tidal_popularity", - "description": "

Track's Tidal popularity.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.tiktok_posts", - "description": "

TikTok posts featuring this track.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.tiktok_top_videos_likes", - "description": "

Sum of likes of the top TikTok videos featuring this artist.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.tiktok_top_videos_views", - "description": "

Sum of views of the top TikTok videos featuring this artist.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.youtube_likes", - "description": "

Number of YouTube likes.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.youtube_playlist_count", - "description": "

Number of YouTube playlists that include this track.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "latest.youtube_views", - "description": "

Number of YouTube views.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "monthly_diff", - "description": "

Object with the monthly change in track stats values.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.airplay_streams", - "description": "

Airplay streams (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.amazon_playlist_count", - "description": "

Number of Amazon playlists that include this track (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.deezer_playlist_count", - "description": "

Number of Deezer playlists that include this track (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.itunes_playlist_count", - "description": "

Number of iTunes playlists that include this track (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.shazam_count", - "description": "

Number of Shazam plays (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.siriusxm_streams", - "description": "

Number of SiriusXM plays (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.soundcloud_plays", - "description": "

Number of SoundCloud plays (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.spotify_playlist_count", - "description": "

Number of Spotify playlists that include this track (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.spotify_playlist_total_reach", - "description": "

Number of Spotify playlists total reach (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.spotify_plays", - "description": "

Number of Spotify plays (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.spotify_popularity", - "description": "

Track's Spotify popularity (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.spotify_ed_playlist_count", - "description": "

Number of Spotify editorial playlists that include this track (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.spotify_ed_playlist_total_reach", - "description": "

Number of Spotify editorial playlists total reach (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly.tidal_popularity", - "description": "

Track's Tidal popularity (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.tiktok_posts", - "description": "

TikTok posts featuring this track (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.tiktok_top_videos_likes", - "description": "

Sum of likes of the top TikTok videos featuring this artist (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.tiktok_top_videos_views", - "description": "

Sum of views of the top TikTok videos featuring this artist (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.youtube_likes", - "description": "

Number of YouTube likes (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.youtube_playlist_count", - "description": "

Number of YouTube playlists that include this track (monthly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff.youtube_views", - "description": "

Number of YouTube views (monthly change).

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "monthly_diff_percent", - "description": "

Objet with the monthly change in track stats values in percentage.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "monthly_diff_percent.rank", - "description": "

Rank change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.rank.rank_overall", - "description": "

Rank change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.airplay_streams", - "description": "

Airplay streams change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.amazon_playlist_count", - "description": "

Number of Amazon playlists that include this track change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.deezer_playlist_count", - "description": "

Number of Deezer playlists that include this track change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.itunes_playlist_count", - "description": "

Number of iTunes playlists that include this track change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.shazam_count", - "description": "

Number of Shazam plays change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.siriusxm_streams", - "description": "

Number of SiriusXM plays change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.soundcloud_plays", - "description": "

Number of SoundCloud plays change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.spotify_playlist_count", - "description": "

Number of Spotify playlists that include this track change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.spotify_playlist_total_reach", - "description": "

Number of Spotify playlists total reach change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.spotify_plays", - "description": "

Number of Spotify plays change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.spotify_popularity", - "description": "

Track's Spotify popularity change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.spotify_ed_playlist_count", - "description": "

Number of Spotify editorial playlists that include this track change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.spotify_ed_playlist_total_reach", - "description": "

Number of Spotify editorial playlists total reach change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.tidal_popularity", - "description": "

Track's Tidal popularity change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.tiktok_posts", - "description": "

TikTok posts featuring this track change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.tiktok_top_videos_likes", - "description": "

Sum of likes of the top TikTok videos featuring this artist change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.tiktok_top_videos_views", - "description": "

Sum of views of the top TikTok videos featuring this artist change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.youtube_likes", - "description": "

Number of YouTube likes change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.youtube_playlist_count", - "description": "

Number of YouTube playlists that include this track change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "monthly_diff_percent.youtube_views", - "description": "

Number of YouTube views change in percentage.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "weekly_diff", - "description": "

Object with the weekly change in track stats values.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.airplay_streams", - "description": "

Airplay streams (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.amazon_playlist_count", - "description": "

Number of Amazon playlists that include this track (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.deezer_playlist_count", - "description": "

Number of Deezer playlists that include this track (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.itunes_playlist_count", - "description": "

Number of iTunes playlists that include this track (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.shazam_count", - "description": "

Number of Shazam plays (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.siriusxm_streams", - "description": "

Number of SiriusXM plays (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.soundcloud_plays", - "description": "

Number of SoundCloud plays (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.spotify_playlist_count", - "description": "

Number of Spotify playlists that include this track (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.spotify_playlist_total_reach", - "description": "

Number of Spotify playlists total reach (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.spotify_plays", - "description": "

Number of Spotify plays (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.spotify_popularity", - "description": "

Track's Spotify popularity (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.spotify_ed_playlist_count", - "description": "

Number of Spotify editorial playlists that include this track (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.spotify_ed_playlist_total_reach", - "description": "

Number of Spotify editorial playlists total reach (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly.tidal_popularity", - "description": "

Track's Tidal popularity (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.tiktok_posts", - "description": "

TikTok posts featuring this track (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.tiktok_top_videos_likes", - "description": "

Sum of likes of the top TikTok videos featuring this artist (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.tiktok_top_videos_views", - "description": "

Sum of views of the top TikTok videos featuring this artist (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.youtube_likes", - "description": "

Number of YouTube likes (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.youtube_playlist_count", - "description": "

Number of YouTube playlists that include this track (weekly change).

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff.youtube_views", - "description": "

Number of YouTube views (weekly change).

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "weekly_diff_percent", - "description": "

Objet with the weekly change in track stats values in percentage.

" - }, - { - "group": "Success 200", - "type": "Object", - "optional": false, - "field": "weekly_diff_percent.rank", - "description": "

Rank change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.rank.rank_overall", - "description": "

Rank change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.airplay_streams", - "description": "

Airplay streams change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.amazon_playlist_count", - "description": "

Number of Amazon playlists that include this track change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.deezer_playlist_count", - "description": "

Number of Deezer playlists that include this track change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.itunes_playlist_count", - "description": "

Number of iTunes playlists that include this track change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.shazam_count", - "description": "

Number of Shazam plays change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.siriusxm_streams", - "description": "

Number of SiriusXM plays change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.soundcloud_plays", - "description": "

Number of SoundCloud plays change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.spotify_playlist_count", - "description": "

Number of Spotify playlists that include this track change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.spotify_playlist_total_reach", - "description": "

Number of Spotify playlists total reach change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.spotify_plays", - "description": "

Number of Spotify plays change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.spotify_popularity", - "description": "

Track's Spotify popularity change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.spotify_ed_playlist_count", - "description": "

Number of Spotify editorial playlists that include this track change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.spotify_ed_playlist_total_reach", - "description": "

Number of Spotify editorial playlists total reach change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.tidal_popularity", - "description": "

Track's Tidal popularity change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.tiktok_posts", - "description": "

TikTok posts featuring this track change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.tiktok_top_videos_likes", - "description": "

Sum of likes of the top TikTok videos featuring this artist change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.tiktok_top_videos_views", - "description": "

Sum of views of the top TikTok videos featuring this artist change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.youtube_likes", - "description": "

Number of YouTube likes change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.youtube_playlist_count", - "description": "

Number of YouTube playlists that include this track change in percentage.

" - }, - { - "group": "Success 200", - "type": "Number", - "optional": false, - "field": "weekly_diff_percent.youtube_views", - "description": "

Number of YouTube views change in percentage.

" - } - ] - }, - "examples": [ - { - "title": "Success-Response (default)", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"cm_track\": 149018957,\n \"album\": [\n {\n \"id\": 45564975,\n \"label\": \"Republic Records\",\n \"name\": \"Golden (from the Netflix film KPop Demon Hunters)\",\n \"ordering\": 0,\n \"release_date\": \"2025-06-19\",\n \"upc\": \"602478712197\"\n }\n ],\n \"artist\": [\n {\n \"id\": 13915334,\n \"name\": \"HUNTR/X\"\n },\n {\n \"id\": 13937011,\n \"name\": \"EJAE\"\n },\n {\n \"id\": 1102960,\n \"name\": \"AUDREY NUNA\"\n },\n {\n \"id\": 1646962,\n \"name\": \"REI AMI\"\n },\n {\n \"id\": 13915335,\n \"name\": \"KPop Demon Hunters Cast\"\n }\n ],\n \"explicit\": false,\n \"genre\": [\n \"pop\",\n \"k-pop\",\n \"pop r&b\"\n ],\n \"genre_smart_ordered\": [\n {\n \"1\": 501120\n },\n {\n \"2\": 501158\n },\n {\n \"3\": 510472\n },\n {\n \"4\": 501139\n },\n {\n \"5\": 501586\n }\n ],\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b2734dcb6c5df15cf74596ab25a4\",\n \"isrc\": \"QZ8BZ2513510\",\n \"label\": null,\n \"name\": \"Golden\",\n \"score\": 99.82791623840855,\n \"songwriter\": [],\n \"mood_smart_ordered\": [\n {\n \"1\": 504952\n },\n {\n \"2\": 505081\n },\n {\n \"3\": 504925\n },\n {\n \"4\": 505090\n }\n ],\n \"activity_smart_ordered\": [\n {\n \"1\": 505265\n },\n {\n \"2\": 504884\n },\n {\n \"3\": 505268\n },\n {\n \"4\": 505258\n },\n {\n \"5\": 504898\n },\n {\n \"6\": 504889\n },\n {\n \"7\": 505261\n },\n {\n \"8\": 505250\n }\n ],\n \"moods\": [\n {\n \"id\": 504952,\n \"name\": \"empowering\",\n \"isParentTag\": false,\n \"rank\": 1\n },\n {\n \"id\": 505081,\n \"name\": \"uplifting\",\n \"isParentTag\": false,\n \"rank\": 2\n },\n {\n \"id\": 504925,\n \"name\": \"catchy\",\n \"isParentTag\": false,\n \"rank\": 3\n },\n {\n \"id\": 505090,\n \"name\": \"youthful\",\n \"isParentTag\": false,\n \"rank\": 4\n }\n ],\n \"activities\": [\n {\n \"id\": 504884,\n \"name\": \"summer\",\n \"isParentTag\": false,\n \"rank\": 2\n },\n {\n \"id\": 504898,\n \"name\": \"gaming\",\n \"isParentTag\": false,\n \"rank\": 5\n },\n {\n \"id\": 504889,\n \"name\": \"workout\",\n \"isParentTag\": false,\n \"rank\": 6\n },\n {\n \"id\": 505261,\n \"name\": \"sleep\",\n \"isParentTag\": false,\n \"rank\": 7\n },\n {\n \"id\": 505250,\n \"name\": \"morning\",\n \"isParentTag\": false,\n \"rank\": 8\n }\n ],\n \"shortlists\": [],\n \"cm_track_cluster\": {\n \"cluster_id\": 149018957,\n \"track_types\": [\n \"original\",\n \"clean\"\n ]\n },\n \"latest\": {\n \"rank\": {\n \"perc_overall\": 1,\n \"rank_overall\": 1,\n \"score_100_overall\": 99.82791623840855,\n \"score_overall\": 0.7426293581687137,\n \"score_percentile\": 0.999999\n },\n \"airplay_streams\": 237386,\n \"amazon_playlist_count\": 65,\n \"deezer_playlist_count\": 155,\n \"itunes_playlist_count\": 282,\n \"shazam_count\": 4466864,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": 902212,\n \"spotify_playlist_count\": 72180,\n \"spotify_playlist_total_reach\": 221100804,\n \"spotify_plays\": 984460584,\n \"spotify_popularity\": 99,\n \"spotify_ed_playlist_count\": 109,\n \"spotify_ed_playlist_total_reach\": 107400371,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 1555700,\n \"tiktok_top_videos_likes\": 278228298,\n \"tiktok_top_videos_views\": 3093274457,\n \"youtube_likes\": 7759976,\n \"youtube_playlist_count\": 109,\n \"youtube_views\": 1047872324,\n \"release_date\": null,\n \"score\": 99.82791623840855\n },\n \"monthly_diff\": {\n \"rank\": {\n \"rank_overall\": 0\n },\n \"airplay_streams\": 96022,\n \"amazon_playlist_count\": 0,\n \"deezer_playlist_count\": 7,\n \"itunes_playlist_count\": 8,\n \"shazam_count\": 1441537,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": 351602,\n \"spotify_playlist_count\": 16817,\n \"spotify_playlist_total_reach\": -1583529,\n \"spotify_plays\": 195613736,\n \"spotify_popularity\": -1,\n \"spotify_ed_playlist_count\": -1,\n \"spotify_ed_playlist_total_reach\": -3056701,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 215326,\n \"tiktok_top_videos_likes\": 38377648,\n \"tiktok_top_videos_views\": 572600840,\n \"youtube_likes\": 1079479,\n \"youtube_playlist_count\": 44,\n \"youtube_views\": 220295599,\n \"release_date\": null\n },\n \"weekly_diff\": {\n \"rank\": {\n \"rank_overall\": -1\n },\n \"airplay_streams\": 25547,\n \"amazon_playlist_count\": -1,\n \"deezer_playlist_count\": 2,\n \"itunes_playlist_count\": -1,\n \"shazam_count\": 273000,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": 88014,\n \"spotify_playlist_count\": 4246,\n \"spotify_playlist_total_reach\": -2076679,\n \"spotify_plays\": 45111490,\n \"spotify_popularity\": 0,\n \"spotify_ed_playlist_count\": -4,\n \"spotify_ed_playlist_total_reach\": -1454474,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 5049,\n \"tiktok_top_videos_likes\": 368817,\n \"tiktok_top_videos_views\": 5087851,\n \"youtube_likes\": 196648,\n \"youtube_playlist_count\": 23,\n \"youtube_views\": 41744431,\n \"release_date\": null\n },\n \"monthly_diff_percent\": {\n \"rank\": {\n \"rank_overall\": 0\n },\n \"airplay_streams\": 67.9254,\n \"amazon_playlist_count\": 0,\n \"deezer_playlist_count\": 4.7297,\n \"itunes_playlist_count\": 2.9197,\n \"shazam_count\": 48.1973,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": 63.8568,\n \"spotify_playlist_count\": 30.3759,\n \"spotify_playlist_total_reach\": -0.7111,\n \"spotify_plays\": 24.7974,\n \"spotify_popularity\": -1,\n \"spotify_ed_playlist_count\": -0.9091,\n \"spotify_ed_playlist_total_reach\": -2.7673,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 16.0646,\n \"tiktok_top_videos_likes\": 16.0006,\n \"tiktok_top_videos_views\": 22.7162,\n \"youtube_likes\": 16.1587,\n \"youtube_playlist_count\": 67.6923,\n \"youtube_views\": 26.6194,\n \"release_date\": null\n },\n \"weekly_diff_percent\": {\n \"rank\": {\n \"rank_overall\": -50\n },\n \"airplay_streams\": 12.0596,\n \"amazon_playlist_count\": -1.5152,\n \"deezer_playlist_count\": 1.3072,\n \"itunes_playlist_count\": -0.3534,\n \"shazam_count\": 6.5634,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": 10.8099,\n \"spotify_playlist_count\": 6.2502,\n \"spotify_playlist_total_reach\": -0.9305,\n \"spotify_plays\": 4.8024,\n \"spotify_popularity\": 0,\n \"spotify_ed_playlist_count\": -3.5398,\n \"spotify_ed_playlist_total_reach\": -1.3362,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 0.3256,\n \"tiktok_top_videos_likes\": 0.1327,\n \"tiktok_top_videos_views\": 0.1648,\n \"youtube_likes\": 2.6,\n \"youtube_playlist_count\": 26.7442,\n \"youtube_views\": 4.149,\n \"release_date\": null\n }\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (with sortColumn)", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"cm_track\": 138061814,\n \"album\": [\n {\n \"id\": 39667639,\n \"label\": \"Atlantic Records\",\n \"name\": \"APT.\",\n \"release_date\": \"2024-10-17\",\n \"upc\": \"075679628312\"\n }\n ],\n \"artist\": [\n {\n \"id\": 3501,\n \"name\": \"Bruno Mars\"\n }\n ],\n \"explicit\": null,\n \"genre\": [\n \"pop\"\n ],\n \"genre_smart_ordered\": [\n {\n \"1\": 501120\n }\n ],\n \"image_url\": \"https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/2d/14/66/2d14661b-345b-d0f3-4e86-56ef626e68aa/075679629203.jpg/160x160bb.jpg\",\n \"isrc\": \"USAT22409184\",\n \"label\": null,\n \"name\": \"APT.\",\n \"score\": 0,\n \"songwriter\": [\n \"ROGET CHAHAYED\",\n \"BRODY BROWN\",\n \"HENRY RUSSELL WALTER\",\n \"PHILIP MARTIN II LAWRENCE\",\n \"NICKY CHINN\",\n \"AMY ALLEN\",\n \"THERON MAKIEL THOMAS\",\n \"PETER GENE HERNANDEZ\",\n \"MIKE CHAPMAN\",\n \"ROSEANNE PARK\",\n \"OMER FEDI\"\n ],\n \"mood_smart_ordered\": [\n {\n \"1\": 504924\n },\n {\n \"2\": 504927\n },\n {\n \"3\": 504972\n },\n {\n \"4\": 505032\n },\n {\n \"5\": 504909\n },\n {\n \"6\": 504978\n }\n ],\n \"activity_smart_ordered\": [\n {\n \"1\": 504872\n },\n {\n \"2\": 504876\n }\n ],\n \"moods\": [\n {\n \"id\": 504924,\n \"name\": \"carefree\",\n \"isParentTag\": false,\n \"rank\": 1\n },\n {\n \"id\": 504927,\n \"name\": \"celebratory\",\n \"isParentTag\": false,\n \"rank\": 2\n },\n {\n \"id\": 504972,\n \"name\": \"flirty\",\n \"isParentTag\": false,\n \"rank\": 3\n },\n {\n \"id\": 505032,\n \"name\": \"playful\",\n \"isParentTag\": false,\n \"rank\": 4\n },\n {\n \"id\": 504909,\n \"name\": \"affectionate\",\n \"isParentTag\": false,\n \"rank\": 5\n },\n {\n \"id\": 504978,\n \"name\": \"fun\",\n \"isParentTag\": false,\n \"rank\": 6\n }\n ],\n \"activities\": [\n {\n \"id\": 504872,\n \"name\": \"dancy\",\n \"isParentTag\": false,\n \"rank\": 1\n },\n {\n \"id\": 504876,\n \"name\": \"partying\",\n \"isParentTag\": false,\n \"rank\": 2\n }\n ],\n \"shortlists\": [],\n \"cm_track_cluster\": {\n \"cluster_id\": 138061814,\n \"track_types\": [\n \"original\"\n ]\n },\n \"latest\": {\n \"rank\": {\n \"perc_overall\": 0.961852,\n \"rank_overall\": 2520562,\n \"score_overall\": 0.004567863556606207,\n \"score_percentile\": 0.788075\n },\n \"airplay_streams\": null,\n \"amazon_playlist_count\": 232,\n \"deezer_playlist_count\": null,\n \"itunes_playlist_count\": 269,\n \"shazam_count\": 11912292,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": null,\n \"spotify_playlist_count\": null,\n \"spotify_playlist_total_reach\": null,\n \"spotify_plays\": null,\n \"spotify_popularity\": null,\n \"spotify_ed_playlist_count\": null,\n \"spotify_ed_playlist_total_reach\": null,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 2451,\n \"tiktok_top_videos_likes\": 277150,\n \"tiktok_top_videos_views\": 8468453,\n \"youtube_likes\": null,\n \"youtube_playlist_count\": null,\n \"youtube_views\": null,\n \"release_date\": null,\n \"score\": 0\n },\n \"monthly_diff\": {\n \"rank\": {\n \"rank_overall\": 1403182\n },\n \"airplay_streams\": null,\n \"amazon_playlist_count\": 7,\n \"deezer_playlist_count\": null,\n \"itunes_playlist_count\": -10,\n \"shazam_count\": 202207,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": null,\n \"spotify_playlist_count\": null,\n \"spotify_playlist_total_reach\": null,\n \"spotify_plays\": null,\n \"spotify_popularity\": null,\n \"spotify_ed_playlist_count\": null,\n \"spotify_ed_playlist_total_reach\": null,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 147,\n \"tiktok_top_videos_likes\": 0,\n \"tiktok_top_videos_views\": 0,\n \"youtube_likes\": null,\n \"youtube_playlist_count\": null,\n \"youtube_views\": null,\n \"release_date\": null\n },\n \"weekly_diff\": {\n \"rank\": {\n \"rank_overall\": 2496228\n },\n \"airplay_streams\": null,\n \"amazon_playlist_count\": 2,\n \"deezer_playlist_count\": null,\n \"itunes_playlist_count\": -2,\n \"shazam_count\": 41951,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": null,\n \"spotify_playlist_count\": null,\n \"spotify_playlist_total_reach\": null,\n \"spotify_plays\": null,\n \"spotify_popularity\": null,\n \"spotify_ed_playlist_count\": null,\n \"spotify_ed_playlist_total_reach\": null,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 29,\n \"tiktok_top_videos_likes\": 0,\n \"tiktok_top_videos_views\": 0,\n \"youtube_likes\": null,\n \"youtube_playlist_count\": null,\n \"youtube_views\": null,\n \"release_date\": null\n },\n \"monthly_diff_percent\": {\n \"rank\": {\n \"rank_overall\": 125.5779\n },\n \"airplay_streams\": null,\n \"amazon_playlist_count\": 3.1111,\n \"deezer_playlist_count\": null,\n \"itunes_playlist_count\": -3.5842,\n \"shazam_count\": 1.7293,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": null,\n \"spotify_playlist_count\": null,\n \"spotify_playlist_total_reach\": null,\n \"spotify_plays\": null,\n \"spotify_popularity\": null,\n \"spotify_ed_playlist_count\": null,\n \"spotify_ed_playlist_total_reach\": null,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 6.4843,\n \"tiktok_top_videos_likes\": 0,\n \"tiktok_top_videos_views\": 0,\n \"youtube_likes\": null,\n \"youtube_playlist_count\": null,\n \"youtube_views\": null,\n \"release_date\": null\n },\n \"weekly_diff_percent\": {\n \"rank\": {\n \"rank_overall\": 10258.1902\n },\n \"airplay_streams\": null,\n \"amazon_playlist_count\": 0.8696,\n \"deezer_playlist_count\": null,\n \"itunes_playlist_count\": -0.738,\n \"shazam_count\": 0.3539,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": null,\n \"spotify_playlist_count\": null,\n \"spotify_playlist_total_reach\": null,\n \"spotify_plays\": null,\n \"spotify_popularity\": null,\n \"spotify_ed_playlist_count\": null,\n \"spotify_ed_playlist_total_reach\": null,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 1.2159,\n \"tiktok_top_videos_likes\": 0,\n \"tiktok_top_videos_views\": 0,\n \"youtube_likes\": null,\n \"youtube_playlist_count\": null,\n \"youtube_views\": null,\n \"release_date\": null\n }\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (with genres filter)", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"cm_track\": 149018957,\n \"album\": [\n {\n \"id\": 45564975,\n \"label\": \"Republic Records\",\n \"name\": \"Golden (from the Netflix film KPop Demon Hunters)\",\n \"ordering\": 0,\n \"release_date\": \"2025-06-19\",\n \"upc\": \"602478712197\"\n }\n ],\n \"artist\": [\n {\n \"id\": 13915334,\n \"name\": \"HUNTR/X\"\n },\n {\n \"id\": 13937011,\n \"name\": \"EJAE\"\n },\n {\n \"id\": 1102960,\n \"name\": \"AUDREY NUNA\"\n },\n {\n \"id\": 1646962,\n \"name\": \"REI AMI\"\n },\n {\n \"id\": 13915335,\n \"name\": \"KPop Demon Hunters Cast\"\n }\n ],\n \"explicit\": false,\n \"genre\": [\n \"pop\",\n \"k-pop\",\n \"pop r&b\"\n ],\n \"genre_smart_ordered\": [\n {\n \"1\": 501120\n },\n {\n \"2\": 501158\n },\n {\n \"3\": 510472\n },\n {\n \"4\": 501139\n },\n {\n \"5\": 501586\n }\n ],\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b2734dcb6c5df15cf74596ab25a4\",\n \"isrc\": \"QZ8BZ2513510\",\n \"label\": null,\n \"name\": \"Golden\",\n \"score\": 99.82791623840855,\n \"songwriter\": [],\n \"mood_smart_ordered\": [\n {\n \"1\": 504952\n },\n {\n \"2\": 505081\n },\n {\n \"3\": 504925\n },\n {\n \"4\": 505090\n }\n ],\n \"activity_smart_ordered\": [\n {\n \"1\": 505265\n },\n {\n \"2\": 504884\n },\n {\n \"3\": 505268\n },\n {\n \"4\": 505258\n },\n {\n \"5\": 504898\n },\n {\n \"6\": 504889\n },\n {\n \"7\": 505261\n },\n {\n \"8\": 505250\n }\n ],\n \"moods\": [\n {\n \"id\": 504952,\n \"name\": \"empowering\",\n \"isParentTag\": false,\n \"rank\": 1\n },\n {\n \"id\": 505081,\n \"name\": \"uplifting\",\n \"isParentTag\": false,\n \"rank\": 2\n },\n {\n \"id\": 504925,\n \"name\": \"catchy\",\n \"isParentTag\": false,\n \"rank\": 3\n },\n {\n \"id\": 505090,\n \"name\": \"youthful\",\n \"isParentTag\": false,\n \"rank\": 4\n }\n ],\n \"activities\": [\n {\n \"id\": 504884,\n \"name\": \"summer\",\n \"isParentTag\": false,\n \"rank\": 2\n },\n {\n \"id\": 504898,\n \"name\": \"gaming\",\n \"isParentTag\": false,\n \"rank\": 5\n },\n {\n \"id\": 504889,\n \"name\": \"workout\",\n \"isParentTag\": false,\n \"rank\": 6\n },\n {\n \"id\": 505261,\n \"name\": \"sleep\",\n \"isParentTag\": false,\n \"rank\": 7\n },\n {\n \"id\": 505250,\n \"name\": \"morning\",\n \"isParentTag\": false,\n \"rank\": 8\n }\n ],\n \"shortlists\": [],\n \"cm_track_cluster\": {\n \"cluster_id\": 149018957,\n \"track_types\": [\n \"original\",\n \"clean\"\n ]\n },\n \"latest\": {\n \"rank\": {\n \"perc_overall\": 1,\n \"rank_overall\": 1,\n \"score_100_overall\": 99.82791623840855,\n \"score_overall\": 0.7426293581687137,\n \"score_percentile\": 0.999999\n },\n \"airplay_streams\": 237386,\n \"amazon_playlist_count\": 65,\n \"deezer_playlist_count\": 155,\n \"itunes_playlist_count\": 282,\n \"shazam_count\": 4466864,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": 902212,\n \"spotify_playlist_count\": 72180,\n \"spotify_playlist_total_reach\": 221100804,\n \"spotify_plays\": 984460584,\n \"spotify_popularity\": 99,\n \"spotify_ed_playlist_count\": 109,\n \"spotify_ed_playlist_total_reach\": 107400371,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 1555700,\n \"tiktok_top_videos_likes\": 278228298,\n \"tiktok_top_videos_views\": 3093274457,\n \"youtube_likes\": 7759976,\n \"youtube_playlist_count\": 109,\n \"youtube_views\": 1047872324,\n \"release_date\": null,\n \"score\": 99.82791623840855\n },\n \"monthly_diff\": {\n \"rank\": {\n \"rank_overall\": 0\n },\n \"airplay_streams\": 96022,\n \"amazon_playlist_count\": 0,\n \"deezer_playlist_count\": 7,\n \"itunes_playlist_count\": 8,\n \"shazam_count\": 1441537,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": 351602,\n \"spotify_playlist_count\": 16817,\n \"spotify_playlist_total_reach\": -1583529,\n \"spotify_plays\": 195613736,\n \"spotify_popularity\": -1,\n \"spotify_ed_playlist_count\": -1,\n \"spotify_ed_playlist_total_reach\": -3056701,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 215326,\n \"tiktok_top_videos_likes\": 38377648,\n \"tiktok_top_videos_views\": 572600840,\n \"youtube_likes\": 1079479,\n \"youtube_playlist_count\": 44,\n \"youtube_views\": 220295599,\n \"release_date\": null\n },\n \"weekly_diff\": {\n \"rank\": {\n \"rank_overall\": -1\n },\n \"airplay_streams\": 25547,\n \"amazon_playlist_count\": -1,\n \"deezer_playlist_count\": 2,\n \"itunes_playlist_count\": -1,\n \"shazam_count\": 273000,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": 88014,\n \"spotify_playlist_count\": 4246,\n \"spotify_playlist_total_reach\": -2076679,\n \"spotify_plays\": 45111490,\n \"spotify_popularity\": 0,\n \"spotify_ed_playlist_count\": -4,\n \"spotify_ed_playlist_total_reach\": -1454474,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 5049,\n \"tiktok_top_videos_likes\": 368817,\n \"tiktok_top_videos_views\": 5087851,\n \"youtube_likes\": 196648,\n \"youtube_playlist_count\": 23,\n \"youtube_views\": 41744431,\n \"release_date\": null\n },\n \"monthly_diff_percent\": {\n \"rank\": {\n \"rank_overall\": 0\n },\n \"airplay_streams\": 67.9254,\n \"amazon_playlist_count\": 0,\n \"deezer_playlist_count\": 4.7297,\n \"itunes_playlist_count\": 2.9197,\n \"shazam_count\": 48.1973,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": 63.8568,\n \"spotify_playlist_count\": 30.3759,\n \"spotify_playlist_total_reach\": -0.7111,\n \"spotify_plays\": 24.7974,\n \"spotify_popularity\": -1,\n \"spotify_ed_playlist_count\": -0.9091,\n \"spotify_ed_playlist_total_reach\": -2.7673,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 16.0646,\n \"tiktok_top_videos_likes\": 16.0006,\n \"tiktok_top_videos_views\": 22.7162,\n \"youtube_likes\": 16.1587,\n \"youtube_playlist_count\": 67.6923,\n \"youtube_views\": 26.6194,\n \"release_date\": null\n },\n \"weekly_diff_percent\": {\n \"rank\": {\n \"rank_overall\": -50\n },\n \"airplay_streams\": 12.0596,\n \"amazon_playlist_count\": -1.5152,\n \"deezer_playlist_count\": 1.3072,\n \"itunes_playlist_count\": -0.3534,\n \"shazam_count\": 6.5634,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": 10.8099,\n \"spotify_playlist_count\": 6.2502,\n \"spotify_playlist_total_reach\": -0.9305,\n \"spotify_plays\": 4.8024,\n \"spotify_popularity\": 0,\n \"spotify_ed_playlist_count\": -3.5398,\n \"spotify_ed_playlist_total_reach\": -1.3362,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 0.3256,\n \"tiktok_top_videos_likes\": 0.1327,\n \"tiktok_top_videos_views\": 0.1648,\n \"youtube_likes\": 2.6,\n \"youtube_playlist_count\": 26.7442,\n \"youtube_views\": 4.149,\n \"release_date\": null\n }\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (with ranges filters)", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"cm_track\": 134780977,\n \"album\": [\n {\n \"id\": 38039185,\n \"label\": \"Interscope\",\n \"name\": \"Die With A Smile\",\n \"ordering\": 0,\n \"release_date\": \"2024-08-16\",\n \"upc\": \"602475093060\"\n }\n ],\n \"artist\": [\n {\n \"id\": 3188,\n \"name\": \"Lady Gaga\"\n },\n {\n \"id\": 3501,\n \"name\": \"Bruno Mars\"\n }\n ],\n \"explicit\": false,\n \"genre\": [\n \"wrapped\",\n \"chill pop\",\n \"therapy\"\n ],\n \"genre_smart_ordered\": [\n {\n \"1\": 510175\n },\n {\n \"2\": 510408\n },\n {\n \"3\": 510336\n },\n {\n \"4\": 501120\n },\n {\n \"5\": 510340\n }\n ],\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b27382ea2e9e1858aa012c57cd45\",\n \"isrc\": \"USUM72409273\",\n \"label\": null,\n \"name\": \"Die With A Smile\",\n \"score\": 99.64795716546625,\n \"songwriter\": [\n \"JAMES FAUNTLEROY\",\n \"PETER GENE HERNANDEZ\",\n \"STEFANI GERMANOTTA\",\n \"ANDREW WOTMAN\"\n ],\n \"mood_smart_ordered\": [\n {\n \"1\": 504951\n },\n {\n \"2\": 505012\n },\n {\n \"3\": 504920\n },\n {\n \"4\": 505002\n },\n {\n \"5\": 505015\n },\n {\n \"6\": 505033\n },\n {\n \"7\": 505056\n },\n {\n \"8\": 505062\n }\n ],\n \"activity_smart_ordered\": [\n {\n \"1\": 505265\n },\n {\n \"2\": 505268\n },\n {\n \"3\": 504884\n },\n {\n \"4\": 505258\n },\n {\n \"5\": 505261\n },\n {\n \"6\": 505263\n },\n {\n \"7\": 505250\n },\n {\n \"8\": 504885\n }\n ],\n \"moods\": [\n {\n \"id\": 504951,\n \"name\": \"emotional\",\n \"isParentTag\": false,\n \"rank\": 1\n },\n {\n \"id\": 505012,\n \"name\": \"loving\",\n \"isParentTag\": true,\n \"rank\": 2\n },\n {\n \"id\": 504920,\n \"name\": \"bittersweet\",\n \"isParentTag\": false,\n \"rank\": 3\n },\n {\n \"id\": 505002,\n \"name\": \"intimate\",\n \"isParentTag\": false,\n \"rank\": 4\n },\n {\n \"id\": 505015,\n \"name\": \"melancholic\",\n \"isParentTag\": false,\n \"rank\": 5\n },\n {\n \"id\": 505033,\n \"name\": \"poignant\",\n \"isParentTag\": false,\n \"rank\": 6\n },\n {\n \"id\": 505056,\n \"name\": \"sentimental\",\n \"isParentTag\": false,\n \"rank\": 7\n },\n {\n \"id\": 505062,\n \"name\": \"soothing\",\n \"isParentTag\": false,\n \"rank\": 8\n }\n ],\n \"activities\": [\n {\n \"id\": 504884,\n \"name\": \"summer\",\n \"isParentTag\": false,\n \"rank\": 3\n },\n {\n \"id\": 505261,\n \"name\": \"sleep\",\n \"isParentTag\": false,\n \"rank\": 5\n },\n {\n \"id\": 505263,\n \"name\": \"soft\",\n \"isParentTag\": false,\n \"rank\": 6\n },\n {\n \"id\": 505250,\n \"name\": \"morning\",\n \"isParentTag\": false,\n \"rank\": 7\n },\n {\n \"id\": 504885,\n \"name\": \"coffee\",\n \"isParentTag\": false,\n \"rank\": 8\n }\n ],\n \"shortlists\": [],\n \"cm_track_cluster\": {\n \"cluster_id\": 134780977,\n \"track_types\": [\n \"original\",\n \"clean\"\n ]\n },\n \"latest\": {\n \"rank\": {\n \"perc_overall\": 1,\n \"rank_overall\": 7,\n \"score_100_overall\": 99.64795716546625,\n \"score_overall\": 0.2984829371067861,\n \"score_percentile\": 0.999999\n },\n \"airplay_streams\": 995551,\n \"amazon_playlist_count\": 132,\n \"deezer_playlist_count\": 154,\n \"itunes_playlist_count\": 283,\n \"shazam_count\": 14880813,\n \"siriusxm_streams\": 28,\n \"soundcloud_plays\": 1208001,\n \"spotify_playlist_count\": 335416,\n \"spotify_playlist_total_reach\": 243755971,\n \"spotify_plays\": 3140356944,\n \"spotify_popularity\": 91,\n \"spotify_ed_playlist_count\": 132,\n \"spotify_ed_playlist_total_reach\": 70260713,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 3251900,\n \"tiktok_top_videos_likes\": 804778305,\n \"tiktok_top_videos_views\": 6394416592,\n \"youtube_likes\": 15449880,\n \"youtube_playlist_count\": 248,\n \"youtube_views\": 1677877815,\n \"release_date\": null,\n \"score\": 99.64795716546625\n },\n \"monthly_diff\": {\n \"rank\": {\n \"rank_overall\": 0\n },\n \"airplay_streams\": 42235,\n \"amazon_playlist_count\": -13,\n \"deezer_playlist_count\": 2,\n \"itunes_playlist_count\": -9,\n \"shazam_count\": 174136,\n \"siriusxm_streams\": 4,\n \"soundcloud_plays\": null,\n \"spotify_playlist_count\": 11860,\n \"spotify_playlist_total_reach\": 1474742,\n \"spotify_plays\": 90541785,\n \"spotify_popularity\": -1,\n \"spotify_ed_playlist_count\": -4,\n \"spotify_ed_playlist_total_reach\": -1874539,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 6000,\n \"tiktok_top_videos_likes\": 29784,\n \"tiktok_top_videos_views\": 298289,\n \"youtube_likes\": 279423,\n \"youtube_playlist_count\": -40,\n \"youtube_views\": 56131539,\n \"release_date\": null\n },\n \"weekly_diff\": {\n \"rank\": {\n \"rank_overall\": -1\n },\n \"airplay_streams\": 10200,\n \"amazon_playlist_count\": -8,\n \"deezer_playlist_count\": 0,\n \"itunes_playlist_count\": -3,\n \"shazam_count\": 37673,\n \"siriusxm_streams\": 3,\n \"soundcloud_plays\": 7211,\n \"spotify_playlist_count\": 2934,\n \"spotify_playlist_total_reach\": 556994,\n \"spotify_plays\": 22500311,\n \"spotify_popularity\": 0,\n \"spotify_ed_playlist_count\": 0,\n \"spotify_ed_playlist_total_reach\": 303496,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 0,\n \"tiktok_top_videos_likes\": 19143,\n \"tiktok_top_videos_views\": 222400,\n \"youtube_likes\": 67009,\n \"youtube_playlist_count\": -14,\n \"youtube_views\": 13426644,\n \"release_date\": null\n },\n \"monthly_diff_percent\": {\n \"rank\": {\n \"rank_overall\": 0\n },\n \"airplay_streams\": 4.4303,\n \"amazon_playlist_count\": -8.9655,\n \"deezer_playlist_count\": -1.2987,\n \"itunes_playlist_count\": -3.0822,\n \"shazam_count\": 1.1841,\n \"siriusxm_streams\": 16.6667,\n \"soundcloud_plays\": null,\n \"spotify_playlist_count\": 3.7749,\n \"spotify_playlist_total_reach\": -0.7754,\n \"spotify_plays\": 2.9688,\n \"spotify_popularity\": -1.087,\n \"spotify_ed_playlist_count\": -2.963,\n \"spotify_ed_playlist_total_reach\": -2.5817,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 0.1848,\n \"tiktok_top_videos_likes\": 0.0037,\n \"tiktok_top_videos_views\": 0.0047,\n \"youtube_likes\": 1.8419,\n \"youtube_playlist_count\": -12.8028,\n \"youtube_views\": 3.4612,\n \"release_date\": null\n },\n \"weekly_diff_percent\": {\n \"rank\": {\n \"rank_overall\": -12.5\n },\n \"airplay_streams\": 1.0352,\n \"amazon_playlist_count\": -5.7143,\n \"deezer_playlist_count\": 0,\n \"itunes_playlist_count\": -1.049,\n \"shazam_count\": 0.2538,\n \"siriusxm_streams\": 12,\n \"soundcloud_plays\": 0.6005,\n \"spotify_playlist_count\": 0.8834,\n \"spotify_playlist_total_reach\": 0.2326,\n \"spotify_plays\": 0.7217,\n \"spotify_popularity\": 0,\n \"spotify_ed_playlist_count\": 0,\n \"spotify_ed_playlist_total_reach\": 0.4339,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 0,\n \"tiktok_top_videos_likes\": 0.0024,\n \"tiktok_top_videos_views\": 0.0035,\n \"youtube_likes\": 0.4356,\n \"youtube_playlist_count\": -5.2632,\n \"youtube_views\": 0.8067,\n \"release_date\": null\n }\n },\n ...\n ]\n}", - "type": "json" - }, - { - "title": "Success-Response (with all filters)", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"cm_track\": 155100763,\n \"album\": [\n {\n \"id\": 48274065,\n \"label\": \"Columbia\",\n \"name\": \"Berghain\",\n \"ordering\": 0,\n \"release_date\": \"2025-10-27\",\n \"upc\": \"196873746579\"\n }\n ],\n \"artist\": [\n {\n \"id\": 612801,\n \"name\": \"ROSALÍA\"\n },\n {\n \"id\": 261,\n \"name\": \"Björk\"\n },\n {\n \"id\": 59592,\n \"name\": \"Yves Tumor\"\n }\n ],\n \"explicit\": true,\n \"genre\": [\n \"pop\",\n \"alternative\",\n \"reggaeton\"\n ],\n \"genre_smart_ordered\": [\n {\n \"1\": 501120\n },\n {\n \"2\": 501132\n },\n {\n \"3\": 501208\n },\n {\n \"4\": 501212\n }\n ],\n \"image_url\": \"https://i.scdn.co/image/ab67616d0000b27308256748d3e6c3ed016cab16\",\n \"isrc\": \"USSM12504034\",\n \"label\": null,\n \"name\": \"Berghain\",\n \"score\": 98.87428153528901,\n \"songwriter\": [\n \"NOAH GOLDSTEIN\",\n \"JAKE MILLER\"\n ],\n \"mood_smart_ordered\": [\n {\n \"1\": 504944\n },\n {\n \"2\": 505026\n },\n {\n \"3\": 504964\n },\n {\n \"4\": 504926\n }\n ],\n \"activity_smart_ordered\": [\n {\n \"1\": 505265\n },\n {\n \"2\": 504878\n },\n {\n \"3\": 504875\n },\n {\n \"4\": 504884\n },\n {\n \"5\": 505258\n },\n {\n \"6\": 505268\n },\n {\n \"7\": 505257\n },\n {\n \"8\": 504888\n }\n ],\n \"moods\": [\n {\n \"id\": 504944,\n \"name\": \"dramatic\",\n \"isParentTag\": false,\n \"rank\": 1\n },\n {\n \"id\": 505026,\n \"name\": \"orchestral\",\n \"isParentTag\": false,\n \"rank\": 2\n },\n {\n \"id\": 504964,\n \"name\": \"experimental\",\n \"isParentTag\": false,\n \"rank\": 3\n },\n {\n \"id\": 504926,\n \"name\": \"cathartic\",\n \"isParentTag\": false,\n \"rank\": 4\n }\n ],\n \"activities\": [\n {\n \"id\": 504878,\n \"name\": \"urban\",\n \"isParentTag\": false,\n \"rank\": 2\n },\n {\n \"id\": 504875,\n \"name\": \"focus\",\n \"isParentTag\": false,\n \"rank\": 3\n },\n {\n \"id\": 504884,\n \"name\": \"summer\",\n \"isParentTag\": false,\n \"rank\": 4\n },\n {\n \"id\": 505257,\n \"name\": \"piano\",\n \"isParentTag\": false,\n \"rank\": 7\n },\n {\n \"id\": 504888,\n \"name\": \"running\",\n \"isParentTag\": false,\n \"rank\": 8\n }\n ],\n \"shortlists\": [],\n \"cm_track_cluster\": null,\n \"latest\": {\n \"rank\": {\n \"perc_overall\": 0.999998,\n \"rank_overall\": 162,\n \"score_100_overall\": 98.87428153528901,\n \"score_overall\": 0.1025105288412828,\n \"score_percentile\": 0.999988\n },\n \"airplay_streams\": 1589,\n \"amazon_playlist_count\": 86,\n \"deezer_playlist_count\": 96,\n \"itunes_playlist_count\": 141,\n \"shazam_count\": 168893,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": 8391,\n \"spotify_playlist_count\": 2288,\n \"spotify_playlist_total_reach\": 143436650,\n \"spotify_plays\": 40166398,\n \"spotify_popularity\": 87,\n \"spotify_ed_playlist_count\": 65,\n \"spotify_ed_playlist_total_reach\": 83973622,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 128569,\n \"tiktok_top_videos_likes\": 103976,\n \"tiktok_top_videos_views\": 2325517,\n \"youtube_likes\": 807864,\n \"youtube_playlist_count\": 29,\n \"youtube_views\": 23254367,\n \"release_date\": null,\n \"score\": 98.87428153528901\n },\n \"monthly_diff\": {\n \"rank\": {},\n \"airplay_streams\": 1589,\n \"amazon_playlist_count\": null,\n \"deezer_playlist_count\": null,\n \"itunes_playlist_count\": null,\n \"shazam_count\": 168893,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": 8391,\n \"spotify_playlist_count\": null,\n \"spotify_playlist_total_reach\": null,\n \"spotify_plays\": 40166398,\n \"spotify_popularity\": 87,\n \"spotify_ed_playlist_count\": null,\n \"spotify_ed_playlist_total_reach\": null,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 128569,\n \"tiktok_top_videos_likes\": 103976,\n \"tiktok_top_videos_views\": 2325517,\n \"youtube_likes\": 807864,\n \"youtube_playlist_count\": null,\n \"youtube_views\": 23254367,\n \"release_date\": null\n },\n \"weekly_diff\": {\n \"rank\": {\n \"rank_overall\": -162\n },\n \"airplay_streams\": 749,\n \"amazon_playlist_count\": 17,\n \"deezer_playlist_count\": 17,\n \"itunes_playlist_count\": 8,\n \"shazam_count\": 68925,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": null,\n \"spotify_playlist_count\": 832,\n \"spotify_playlist_total_reach\": 223434,\n \"spotify_plays\": 18670544,\n \"spotify_popularity\": 3,\n \"spotify_ed_playlist_count\": 14,\n \"spotify_ed_playlist_total_reach\": 5361912,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 109491,\n \"tiktok_top_videos_likes\": 74778,\n \"tiktok_top_videos_views\": 2017400,\n \"youtube_likes\": 117647,\n \"youtube_playlist_count\": 24,\n \"youtube_views\": 5883437,\n \"release_date\": null\n },\n \"monthly_diff_percent\": {\n \"rank\": {},\n \"airplay_streams\": null,\n \"amazon_playlist_count\": null,\n \"deezer_playlist_count\": null,\n \"itunes_playlist_count\": null,\n \"shazam_count\": null,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": null,\n \"spotify_playlist_count\": null,\n \"spotify_playlist_total_reach\": null,\n \"spotify_plays\": null,\n \"spotify_popularity\": null,\n \"spotify_ed_playlist_count\": null,\n \"spotify_ed_playlist_total_reach\": null,\n \"tidal_popularity\": null,\n \"tiktok_posts\": null,\n \"tiktok_top_videos_likes\": null,\n \"tiktok_top_videos_views\": null,\n \"youtube_likes\": null,\n \"youtube_playlist_count\": null,\n \"youtube_views\": null,\n \"release_date\": null\n },\n \"weekly_diff_percent\": {\n \"rank\": {\n \"rank_overall\": -50\n },\n \"airplay_streams\": 89.1667,\n \"amazon_playlist_count\": 25.3731,\n \"deezer_playlist_count\": 19.5402,\n \"itunes_playlist_count\": 6.1069,\n \"shazam_count\": 68.9471,\n \"siriusxm_streams\": null,\n \"soundcloud_plays\": null,\n \"spotify_playlist_count\": 66.6667,\n \"spotify_playlist_total_reach\": 0.1645,\n \"spotify_plays\": 86.8565,\n \"spotify_popularity\": 3.5714,\n \"spotify_ed_playlist_count\": 29.7872,\n \"spotify_ed_playlist_total_reach\": 6.9849,\n \"tidal_popularity\": null,\n \"tiktok_posts\": 573.9124,\n \"tiktok_top_videos_likes\": 256.1066,\n \"tiktok_top_videos_views\": 654.7513,\n \"youtube_likes\": 17.0449,\n \"youtube_playlist_count\": 480,\n \"youtube_views\": 33.8694,\n \"release_date\": null\n }\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage (default):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/list/filter", - "type": "curl" - }, - { - "title": "Example usage (with sortColumn):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/list/filter?sortColumn=latest.amazon_playlist_count&sortOrderDesc=true", - "type": "curl" - }, - { - "title": "Example usage (with genres filter):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/list/filter?genres[]=462882&genres[]=462884", - "type": "curl" - }, - { - "title": "Example usage (with ranges filter):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/list/filter?range_period=monthly_diff&min_spotify_plays=1000000&max_tiktok_posts=50000", - "type": "curl" - }, - { - "title": "Example usage (with all filters):", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/list/filter?sortOrderDesc=false&sortColumn=latest.spotify_playlist_total_reach&range_period=weekly_diff&min_amazon_playlist_count=10&min_youtube_playlist_count=1&min_release_date=2010-01-01&genres[]=462882&genres[]=462884&limit=10", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/track.docs.ts", - "groupTitle": "Track" - }, - { - "type": "get", - "url": "/api/track/:id/:type/charts", - "title": "Charts", - "version": "1.0.0", - "name": "getTrackCharts", - "description": "

This API returns the charts for this track. For example, you can retrieve chart records from Spotify Charts, iTunes Charts, Apple Music Charts, Shazam Charts or Beatport Charts.

", - "group": "Track", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "spotify_viral_daily", - "spotify_viral_weekly", - "spotify_top_daily", - "spotify_top_weekly", - "applemusic_top", - "applemusic_daily", - "applemusic_albums", - "itunes_top", - "itunes_albums", - "shazam", - "beatport", - "amazon", - "soundcloud", - "airplay_daily", - "airplay_weekly" - ], - "optional": false, - "field": "type", - "description": "" - }, - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "id", - "description": "

Track Chartmetric ID

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": false, - "field": "since", - "description": "

Start date in ISO date format (e.g. "2020-03-01").

" - }, - { - "group": "Parameter", - "type": "Date", - "optional": true, - "field": "until", - "defaultValue": "today", - "description": "

End date in ISO date format (e.g. "2020-09-01").

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data", - "description": "

List of results

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.id", - "description": "

(Internal system ID, will be deprecated in the future)

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.rank", - "description": "

Chart Position

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.added_at", - "description": "

Date of Data Recorded

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.code2", - "description": "

Charting Country Code

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.plays", - "description": "

Number of plays

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.duration", - "description": "

Duration

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.chart_type", - "description": "

Type of chart

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.gnp", - "description": "

Track gnp

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.pre_rank", - "description": "

Latest Previous Chart Position

" - }, - { - "group": "Success 200", - "type": "integer", - "optional": false, - "field": "data.peak_rank", - "description": "

Best Chart Position

" - }, - { - "group": "Success 200", - "type": "Date", - "optional": false, - "field": "data.peak_date", - "description": "

Date of Best Chart Position

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.peak_plays", - "description": "

Number of plays for Best Chart Position

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.total_plays", - "description": "

Total number of plays

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.name", - "description": "

Track name

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.image_url", - "description": "

Image url

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.spotify_popularity", - "description": "

Popularity on Spotify

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.cm_track", - "description": "

Chartmetric track ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.spotify_artist_names", - "description": "

Spotify artist names

" - }, - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.track_genre", - "description": "

Track genre

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.cm_artist", - "description": "

Chartmetric artist ID

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_names", - "description": "

Artist names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.code2s", - "description": "

Country codes

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.artist_images", - "description": "

Artist images

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.spotify_artist_ids", - "description": "

Spotify artist IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.spotify_track_ids", - "description": "

Spotify track IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.spotify_album_ids", - "description": "

Spotify album IDs

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.spotify_duration_ms", - "description": "

Spotify duration

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_artist_ids", - "description": "

iTunes artist IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_artist_names", - "description": "

iTunes artist names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_track_ids", - "description": "

iTunes track IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_album_ids", - "description": "

iTunes album IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.storefronts", - "description": "

Store fronts

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_ids", - "description": "

Album IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_names", - "description": "

Album names

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_upc", - "description": "

Album upc

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.album_label", - "description": "

Album label

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.itunes_album_id", - "description": "

iTunes album id

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "data.release_dates", - "description": "

Release datea

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "length", - "description": "

Length of the data

" - } - ] - }, - "examples": [ - { - "title": "Success-Response", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": {\n \"data\": [\n {\n \"id\": 10341048,\n \"spotify_id\": 10341048,\n \"rank\": 104,\n \"added_at\": \"2020-03-04T08:00:00.000Z\",\n \"code2\": \"GLOBAL\",\n \"plays\": 1084704,\n \"duration\": \"daily\",\n \"chart_type\": \"regional\",\n \"gnp\": null,\n \"pre_rank\": 105,\n \"peak_rank\": 1,\n \"peak_date\": \"2019-02-08T08:00:00.000Z\",\n \"peak_plays\": 10445538,\n \"total_plays\": 1033605669,\n \"name\": \"7 rings\",\n \"isrc\": \"USUM71900110\",\n \"spotify_album_id\": \"2fYhqwDWXjbpjaIJPEfKFw\",\n \"image_url\": \"https://i.scdn.co/image/38205ab1bdc4b37b68b3ace40989afb6c2c1faa9\",\n \"spotify_popularity\": 86,\n \"cm_track\": 22290726,\n \"spotify_artist_names\": [\n \"Ariana Grande\"\n ],\n \"track_genre\": \"dance pop,pop,post-teen pop\",\n \"cm_artist\": [\n 3963\n ],\n \"artist_names\": [\n \"Ariana Grande\"\n ],\n \"code2s\": [\n \"US\"\n ],\n \"artist_images\": [\n \"https://i.scdn.co/image/b1dfbe843b0b9f54ab2e588f33e7637d2dab065a\"\n ],\n \"artist_covers\": [\n \"https://i.scdn.co/image/4599ff6a6b96d54df41e97a484605f0e517ba278\"\n ],\n \"spotify_artist_ids\": [\n \"66CXWjxzNUsdJxJ2JdwvnR\"\n ],\n \"spotify_track_ids\": [\n \"6ocbgoVGwYJhOv1GgI9NsF\"\n ],\n \"spotify_album_ids\": [\n \"2fYhqwDWXjbpjaIJPEfKFw\"\n ],\n \"spotify_duration_ms\": 178626,\n \"album_ids\": [\n 3532913\n ],\n \"album_names\": [\n \"thank u, next\"\n ],\n \"album_upc\": [\n \"602577490385\"\n ],\n \"album_label\": [\n \"Republic Records\"\n ],\n \"release_dates\": [\n \"2019-02-08T08:00:00.000Z\"\n ]\n },\n ...\n ],\n \"length\": 33\n }\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/22290726/spotify_top_daily/charts?since=2020-03-01&until=2020-03-04", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/track.docs.ts", - "groupTitle": "Track" - }, - { - "type": "get", - "url": "/api/track/:type/:id/get-ids", - "title": "Get Track IDs", - "name": "getTrackIDs", - "version": "1.0.0", - "description": "

Use a specific ID to get all IDs from other domains for the same track. For example, if you have a Spotify ID, you can use this endpoint to get the track's ISRC, iTunes ID, and other IDs. This is useful for cross-referencing IDs from different domains, especially when you only have one ID

IDs from each domain are returned as arrays for flexibility. If a track has multiple IDs in a domain, all IDs will be returned in the array. If a track has no linked ID in a domain, the array will be empty.

", - "group": "Track", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "String", - "allowedValues": [ - "'chartmetric'", - "'isrc'", - "'amazon'", - "'beatport'", - "'deezer'", - "'genius'", - "'itunes'", - "'shazam'", - "'spotify'", - "'soundcloud'", - "'tiktok'", - "'qq'", - "'youtube'" - ], - "optional": false, - "field": "type", - "description": "

Domain type, e.g. 'spotify' to use a Spotify ID

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "id", - "description": "

ID for the domain specified by type (so if type is 'amazon', this would be the Amazon ID).

Note that String is used as a broad type to allow for all types of IDs, but the actual ID format will vary depending on the domain and may be strictly numeric.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "isrc", - "description": "

Track isrc

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "chartmetric_ids", - "description": "

Chartmetric IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "spotify_ids", - "description": "

Spotify IDs, ordered by

  1. Date of first ingestion (chronological)
  2. Latest popularity score (descending) to break ties
" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "itunes_ids", - "description": "

iTunes IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "deezer_ids", - "description": "

Deezer IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "amazon_ids", - "description": "

Amazon IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "youtube_ids", - "description": "

YouTube IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "soundcloud_ids", - "description": "

SoundCloud IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "shazam_ids", - "description": "

Shazam IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "tiktok_ids", - "description": "

TikTok IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "beatport_ids", - "description": "

Beatport IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "qq_ids", - "description": "

QQ IDs

" - }, - { - "group": "Success 200", - "type": "Array", - "optional": false, - "field": "genius_ids", - "description": "

Genius IDs

" - } - ] - }, - "examples": [ - { - "title": "Success-Response", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n {\n \"isrc\": \"GBWHD0801085\",\n \"chartmetric_ids\": [\n 13106618\n ],\n \"spotify_ids\": [\n \"35pIBRfhByXHvWkLBXIloT\"\n ],\n \"itunes_ids\": [\n \"311034817\"\n ],\n \"deezer_ids\": null,\n \"amazon_ids\": null,\n \"youtube_ids\": null,\n \"soundcloud_ids\": null,\n \"shazam_ids\": null,\n \"tiktok_ids\": [\n \"Smooth-Criminal-245073415512956928\",\n \"Smooth-Criminal-Instrumental-version-originally-performed-by-Michael-Jackson-6792293516425824257\"\n ],\n \"beatport_ids\": null,\n \"qq_ids\": null,\n \"genius_ids\": null\n }\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/chartmetric/13106618/get-ids", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/track.docs.ts", - "groupTitle": "Track" - }, - { - "type": "get", - "url": "/api/track/:id/video-trends", - "title": "Video Trends for TikTok Video", - "version": "1.0.0", - "name": "getVideoTrendsForGivenTikTokVideoId", - "description": "

This API returns the trends of a specific TikTok video over time, providing insights into likes, comments, and views.

", - "group": "Track", - "parameter": { - "fields": { - "Parameter": [ - { - "group": "Parameter", - "type": "Integer", - "optional": false, - "field": "fromDaysAgo", - "description": "

The number of days from the current date to start retrieving trend data.

" - }, - { - "group": "Parameter", - "type": "String", - "optional": false, - "field": "id", - "description": "

specific TikTok Video ID to retrieve trends for.

" - } - ] - } - }, - "success": { - "fields": { - "Success 200": [ - { - "group": "Success 200", - "type": "String", - "optional": false, - "field": "data.timestp", - "description": "

Timestamp of the data point in ISO 8601 format

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.likes", - "description": "

Number of likes

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.comments", - "description": "

Number of comments

" - }, - { - "group": "Success 200", - "type": "Integer", - "optional": false, - "field": "data.views", - "description": "

Number of views

" - } - ] - }, - "examples": [ - { - "title": "Success-Response:", - "content": "HTTP/1.1 200 OK\n{\n \"obj\": [\n \n {\n \"timestp\": \"2023-05-30T00:00:00.000Z\",\n \"likes\": 72500,\n \"comments\": 222,\n \"views\": 411800\n },\n ...\n ]\n}", - "type": "json" - } - ] - }, - "examples": [ - { - "title": "Example usage:", - "content": "curl -H 'Authorization: Bearer [ACCESS KEY]' https://api.chartmetric.com/api/track/456531185/video-trends?fromDaysAgo=365", - "type": "curl" - } - ], - "filename": "src/Routers/apiRouter/docs/track.docs.ts", - "groupTitle": "Track" - } -] diff --git a/skills/chart-metric/references/curl-fallback.md b/skills/chart-metric/references/curl-fallback.md deleted file mode 100644 index 09e6a79..0000000 --- a/skills/chart-metric/references/curl-fallback.md +++ /dev/null @@ -1,339 +0,0 @@ -# Chartmetric API - Curl Fallback - -Use these patterns when Python is unavailable. Requires `curl` (universal) and optionally `jq` (for pretty output). - ---- - -## Token Management - -### Get Token (with jq) - -```bash -export CHARTMETRIC_TOKEN=$(curl -s -X POST "https://api.chartmetric.com/api/token" \ - -H "Content-Type: application/json" \ - -d "{\"refreshtoken\":\"$CHARTMETRIC_REFRESH_TOKEN\"}" | jq -r '.token') -``` - -### Get Token (without jq) - -```bash -export CHARTMETRIC_TOKEN=$(curl -s -X POST "https://api.chartmetric.com/api/token" \ - -H "Content-Type: application/json" \ - -d "{\"refreshtoken\":\"$CHARTMETRIC_REFRESH_TOKEN\"}" | grep -o '"token":"[^"]*"' | cut -d'"' -f4) -``` - -### Token Caching (optional) - -```bash -# Cache token to file (expires in ~1 hour) -TOKEN_FILE="/tmp/chartmetric_token" - -get_token() { - if [ -f "$TOKEN_FILE" ] && [ $(($(date +%s) - $(stat -f %m "$TOKEN_FILE" 2>/dev/null || stat -c %Y "$TOKEN_FILE"))) -lt 3500 ]; then - cat "$TOKEN_FILE" - else - TOKEN=$(curl -s -X POST "https://api.chartmetric.com/api/token" \ - -H "Content-Type: application/json" \ - -d "{\"refreshtoken\":\"$CHARTMETRIC_REFRESH_TOKEN\"}" | grep -o '"token":"[^"]*"' | cut -d'"' -f4) - echo "$TOKEN" > "$TOKEN_FILE" - echo "$TOKEN" - fi -} - -export CHARTMETRIC_TOKEN=$(get_token) -``` - ---- - -## Search - -### Search Artists - -```bash -curl -s "https://api.chartmetric.com/api/search?q=Drake&type=artists&limit=5" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### Search Tracks - -```bash -curl -s "https://api.chartmetric.com/api/search?q=One%20Dance&type=tracks&limit=5" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### Search Albums - -```bash -curl -s "https://api.chartmetric.com/api/search?q=Views&type=albums&limit=5" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - ---- - -## Artist Endpoints - -### Get Artist Profile - -```bash -curl -s "https://api.chartmetric.com/api/artist/3380" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### Lookup Artist by Spotify ID - -```bash -# Extract ID from URL if needed: https://open.spotify.com/artist/3TVXtAsR1Inumwj472S9r4 -SPOTIFY_ID="3TVXtAsR1Inumwj472S9r4" -curl -s "https://api.chartmetric.com/api/artist/spotify/$SPOTIFY_ID/get-ids" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### Get Artist Metrics - -```bash -# Valid sources: spotify, instagram, tiktok, twitter, facebook, youtube_channel, -# youtube_artist, soundcloud, deezer, twitch, line, melon, wikipedia, bandsintown -# ⚠️ Use youtube_channel or youtube_artist, NOT youtube - -curl -s "https://api.chartmetric.com/api/artist/3380/stat/spotify" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" - -curl -s "https://api.chartmetric.com/api/artist/3380/stat/youtube_channel" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### Get Where People Listen (Cities) - -```bash -curl -s "https://api.chartmetric.com/api/artist/3380/where-people-listen" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### Get Artist URLs - -```bash -curl -s "https://api.chartmetric.com/api/artist/3380/urls" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### Get Artist Albums - -```bash -curl -s "https://api.chartmetric.com/api/artist/3380/albums" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### Get Artist Tracks - -```bash -curl -s "https://api.chartmetric.com/api/artist/3380/tracks" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### Get Artist Insights - -```bash -curl -s "https://api.chartmetric.com/api/artist/3380/noteworthy-insights" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### Get Artist Career - -```bash -curl -s "https://api.chartmetric.com/api/artist/3380/career" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - ---- - -## Playlist Placements - -### Current Playlists - -```bash -# Platforms: spotify, applemusic, deezer, amazon, youtube -# Status: current, past - -curl -s "https://api.chartmetric.com/api/artist/3380/spotify/current/playlists?limit=50" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### With Filters (Spotify) - -```bash -# Filters: editorial, personalized, chart, thisIs, newMusicFriday, radio, indie, majorCurator, popularIndie, brand - -curl -s "https://api.chartmetric.com/api/artist/3380/spotify/current/playlists?editorial=true&newMusicFriday=true&limit=50" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### With Date Range and Sorting - -```bash -curl -s "https://api.chartmetric.com/api/artist/3380/spotify/current/playlists?since=2025-01-01&sortColumn=followers&limit=100" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### Past Playlists - -```bash -curl -s "https://api.chartmetric.com/api/artist/3380/spotify/past/playlists?limit=50" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - ---- - -## Similar/Related Artists - -### Basic Related Artists - -```bash -curl -s "https://api.chartmetric.com/api/artist/3380/relatedartists?limit=10" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### Advanced Similar Artists (by Configuration) - -```bash -# Config options: audience, mood, genre, musicality (values: high, medium, low) -# At least one config required - -curl -s "https://api.chartmetric.com/api/artist/3380/similar-artists/by-configurations?audience=high&genre=high&limit=10" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - ---- - -## Audience Demographics - -### Instagram Audience - -```bash -curl -s "https://api.chartmetric.com/api/artist/3380/instagram-audience-stats" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### TikTok Audience - -```bash -curl -s "https://api.chartmetric.com/api/artist/3380/tiktok-audience-stats" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### YouTube Audience - -```bash -curl -s "https://api.chartmetric.com/api/artist/3380/youtube-audience-stats" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### Instagram Top Posts - -```bash -curl -s "https://api.chartmetric.com/api/SNS/deepSocial/cm_artist/3380/instagram" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - ---- - -## Track Endpoints - -### Get Track - -```bash -curl -s "https://api.chartmetric.com/api/track/128613854" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### Lookup Track by Spotify ID - -```bash -SPOTIFY_ID="0VjIjW4GlUZAMYd2vXMi3b" -curl -s "https://api.chartmetric.com/api/track/spotify/$SPOTIFY_ID/get-ids" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - ---- - -## Playlist & Curator - -### Get Playlist - -```bash -# Platforms: spotify, apple, deezer, amazon -curl -s "https://api.chartmetric.com/api/playlist/spotify/37i9dQZF1DXcBWIGoYBM5M" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### Get Curator - -```bash -# ⚠️ Must use numeric Chartmetric curator ID, not name -curl -s "https://api.chartmetric.com/api/curator/spotify/1" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - ---- - -## Discovery - -### List Genres - -```bash -curl -s "https://api.chartmetric.com/api/genres" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - -### List Festivals - -```bash -curl -s "https://api.chartmetric.com/api/festival/list" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" -``` - ---- - -## Pretty Output (with jq) - -If `jq` is available, pipe output for readability: - -```bash -curl -s "https://api.chartmetric.com/api/artist/3380" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" | jq . - -# Extract specific fields -curl -s "https://api.chartmetric.com/api/search?q=Drake&type=artists" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN" | jq '.obj.artists[] | {name, id, spotify_id}' -``` - ---- - -## Error Handling - -Check response status: - -```bash -response=$(curl -s -w "\n%{http_code}" "https://api.chartmetric.com/api/artist/3380" \ - -H "Authorization: Bearer $CHARTMETRIC_TOKEN") - -http_code=$(echo "$response" | tail -1) -body=$(echo "$response" | sed '$d') - -if [ "$http_code" != "200" ]; then - echo "Error: HTTP $http_code" - echo "$body" -else - echo "$body" -fi -``` - ---- - -## Common Errors - -| HTTP Code | Cause | Fix | -|-----------|-------|-----| -| 401 | Token expired or invalid | Re-run token command | -| 402 | Subscription issue | Check Chartmetric account | -| 404 | Invalid ID | Verify Chartmetric ID (not Spotify ID) | -| 429 | Rate limited | Wait 60 seconds | diff --git a/skills/chart-metric/references/endpoints.md b/skills/chart-metric/references/endpoints.md deleted file mode 100644 index 564f44a..0000000 --- a/skills/chart-metric/references/endpoints.md +++ /dev/null @@ -1,264 +0,0 @@ -# Chartmetric API - Complete Endpoint Reference - -> Base URL: `https://api.chartmetric.com` -> Full docs JSON: `references/api_data.json` - -## Legend -- ✅ Working on your subscription -- 🔒 Locked (401 - needs higher tier) -- ⚠️ Needs specific params (400 with test data) - ---- - -## Authorization (1 endpoint) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ✅ | POST | `/api/token` | Get API access token | - ---- - -## Album (6 endpoints) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ✅ | GET | `/api/album/:id` | Album metadata | -| ✅ | GET | `/api/album/:id/tracks` | Album tracks | -| ✅ | GET | `/api/album/:id/:platform/:status/playlists` | Album playlists | -| ✅ | GET | `/api/album/:type/:id/get-ids` | Lookup album by platform ID | -| ⚠️ | GET | `/api/album/:id/:platform/:stats` | Album stats | -| ⚠️ | GET | `/api/album/:id/:type/charts` | Album charts | - ---- - -## Artist (35 endpoints) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ✅ | GET | `/api/artist/:id` | Artist metadata | -| ✅ | GET | `/api/artist/:id/albums` | Artist albums | -| ✅ | GET | `/api/artist/:id/tracks` | Artist tracks | -| ✅ | GET | `/api/artist/:id/urls` | Social/streaming URLs | -| ✅ | GET | `/api/artist/:id/milestones` | Career milestones | -| ✅ | GET | `/api/artist/:id/news` | Recent news | -| ✅ | GET | `/api/artist/:id/noteworthy-insights` | AI insights | -| ✅ | GET | `/api/artist/:id/neighboring-artists` | Similar artists | -| ✅ | GET | `/api/artist/:id/riaa` | RIAA certifications | -| ✅ | GET | `/api/artist/:id/artist-rank` | Artist ranking | -| ✅ | GET | `/api/artist/:id/past-artist-rank` | Historical ranking | -| ✅ | GET | `/api/artist/:id/cmStats` | Cached stats & trends | -| ✅ | GET | `/api/artist/:id/career` | Career history | -| ✅ | GET | `/api/artist/:id/stat/:source` | Platform stats (spotify, instagram, etc.) | -| ✅ | GET | `/api/artist/:id/where-people-listen` | Spotify listeners by city | -| ✅ | GET | `/api/artist/:id/:platform/:status/playlists` | Playlist placements | -| ✅ | GET | `/api/artist/:id/venues` | Concert venues | -| ✅ | GET | `/api/artist/:id/tvmaze` | TV appearances | -| ✅ | GET | `/api/artist/:id/instagram-audience-stats` | Instagram audience demographics | -| ✅ | GET | `/api/artist/:id/instagram-audience-stats/dates` | IG audience data dates | -| ✅ | GET | `/api/artist/:id/tiktok-audience-stats` | TikTok audience demographics | -| ✅ | GET | `/api/artist/:id/youtube-audience-stats` | YouTube audience demographics | -| ✅ | GET | `/api/artist/:id/market-coverage-views/youtube` | YouTube views by market | -| ✅ | GET | `/api/artist/:type/:id/get-ids` | Lookup artist by platform ID | -| ✅ | GET | `/api/artist/list/filter` | Filter/discover artists | -| ⚠️ | GET | `/api/artist/anr/by/playlists` | ANR by playlists | -| ⚠️ | GET | `/api/artist/anr/by/social-index` | ANR by social index | -| ⚠️ | GET | `/api/artist/:id/cpp` | Cross-platform performance | -| ⚠️ | GET | `/api/artist/:id/:status/events` | Live events | -| ⚠️ | GET | `/api/artist/:id/top-tracks/:source` | Top tracks by platform | -| ⚠️ | GET | `/api/artist/:id/relatedartists` | Related artists | -| ⚠️ | GET | `/api/artist/:id/similar-artists/by-configurations` | Similar artists (configurable) | -| ⚠️ | GET | `/api/artist/:id/social-audience-stats` | Social audience stats | -| ⚠️ | GET | `/api/artist/:type/list` | List artists by metric | - ---- - -## Brand (3 endpoints) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ✅ | GET | `/api/brand/list` | List all brands | -| ✅ | GET | `/api/brand/list/by/interest` | Brands by interest | -| ⚠️ | GET | `/api/brand/:brandId` | Brand info | - ---- - -## Charts (32 endpoints) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ✅ | GET | `/api/charts/shazam/:country_code/cities` | Shazam cities | -| 🔒 | GET | `/api/charts/` | Charts introduction | -| 🔒 | GET | `/api/charts/airplay/:chart_type` | Airplay charts | -| 🔒 | GET | `/api/charts/amazon/:chart-type` | Amazon charts | -| 🔒 | GET | `/api/charts/applemusic/:chart-type` | Apple Music charts | -| 🔒 | GET | `/api/charts/tiktok/:chart-type` | TikTok charts | -| 🔒 | GET | `/api/charts/youtube/:chart_type` | YouTube charts | -| 🔒 | GET | `/api/charts/itunes/:chart-type` | iTunes charts | -| ⚠️ | GET | `/api/charts/anghami/track/:chartType` | Anghami track charts | -| ⚠️ | GET | `/api/charts/beatport` | Beatport charts | -| ⚠️ | GET | `/api/charts/:platform/countries` | Chart countries | -| ⚠️ | GET | `/api/charts/:streamingType/dates` | Chart dates | -| ⚠️ | GET | `/api/charts/genres/:platform` | Chart genres | -| ⚠️ | GET | `/api/charts/:type/:type_id/:chart_type/cm-score` | Chartmetric score | -| ⚠️ | GET | `/api/charts/circle/album/:chartType` | Circle album charts | -| ⚠️ | GET | `/api/charts/circle/track/:chartType` | Circle track charts | -| ⚠️ | GET | `/api/charts/deezer/` | Deezer charts | -| ⚠️ | GET | `/api/charts/hanteo/album/:chartType` | Hanteo album charts | -| ⚠️ | GET | `/api/charts/hanteo/track/:chartType` | Hanteo track charts | -| ⚠️ | GET | `/api/charts/line_music/album/:chartType` | Line Music album charts | -| ⚠️ | GET | `/api/charts/line_music/track/:chartType` | Line Music track charts | -| ⚠️ | GET | `/api/charts/melon/track/:chartType` | Melon track charts | -| ⚠️ | GET | `/api/charts/pandora/track/:chartType` | Pandora track charts | -| ⚠️ | GET | `/api/charts/qq/` | QQ Music charts | -| ⚠️ | GET | `/api/charts/shazam` | Shazam charts | -| ⚠️ | GET | `/api/charts/soundcloud` | SoundCloud (legacy) | -| ⚠️ | GET | `/api/charts/soundcloud/track/:chartType` | SoundCloud charts | -| ⚠️ | GET | `/api/charts/spotify/artists` | Spotify artist charts | -| ⚠️ | GET | `/api/charts/spotify/freshfind` | Spotify Freshfind | -| ⚠️ | GET | `/api/charts/spotify` | Spotify track charts | -| ⚠️ | GET | `/api/charts/tiktok/tracks/:chart-type` | TikTok track charts | -| ⚠️ | GET | `/api/charts/twitch/users` | Twitch charts | - ---- - -## City (2 endpoints) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ✅ | GET | `/api/city/:id/:source/top-artists` | Top artists in city | -| ⚠️ | GET | `/api/city/:id/:source/top-tracks` | Top tracks in city | - ---- - -## Curator (5 endpoints) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ✅ | GET | `/api/curator/:platform/:id/` | Curator metadata | -| ✅ | GET | `/api/curator/:platform/:id/playlists` | Curator's playlists | -| ✅ | GET | `/api/curator/:platform/:id/stat/:source` | Curator fan metrics | -| ✅ | GET | `/api/curator/:platform/:id/urls` | Curator social URLs | -| ✅ | GET | `/api/curator/:platform/lists` | List curators | - ---- - -## Event (1 endpoint) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ✅ | GET | `/api/event/venue/:venueId` | Events at venue | - ---- - -## Festival (1 endpoint) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ✅ | GET | `/api/festival/list` | List festivals | - ---- - -## Genre (1 endpoint) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ✅ | GET | `/api/genre` | List all genres | - ---- - -## Playlist (9 endpoints) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ✅ | GET | `/api/playlist/:platform/:id` | Playlist metadata | -| ✅ | GET | `/api/playlist/:platform/:id/stats` | Playlist stats over time | -| ✅ | GET | `/api/playlist/:platform/:id/updated` | Last updated time | -| ✅ | GET | `/api/playlist/:platform/lists` | List playlists | -| ⚠️ | GET | `/api/playlist/by/:type/:id/evolution` | Playlist evolution | -| ⚠️ | GET | `/api/playlist/by/:type/:id/playlist-evolution` | Playlist evolution (alt) | -| ⚠️ | GET | `/api/playlist/:platform/:id/journey-progression/:type` | Playlist journey | -| ⚠️ | GET | `/api/playlist/:platform/:id/snapshot` | Playlist snapshot | -| ⚠️ | GET | `/api/playlist/:platform/:id/:span/tracks` | Playlist tracks | - ---- - -## Radio (5 endpoints) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ✅ | GET | `/api/radio/station-list` | Radio station list | -| ⚠️ | GET | `/api/radio/:type/:id/airplay-totals` | Total airplays | -| ⚠️ | GET | `/api/radio/:type/:id/airplay-totals/:entity` | Airplays by entity | -| ⚠️ | GET | `/api/radio/:type/:id/airplays` | Airplay time series | -| ⚠️ | GET | `/api/radio/:type/:id/broadcast-markets` | Broadcast markets | - ---- - -## Recommendation (1 endpoint) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ⚠️ | GET | `/api/playlist/:platform/:id/similarplaylists` | Similar playlists | - ---- - -## SNS (1 endpoint) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ✅ | GET | `/api/SNS/deepSocial/cm_artist/:id/instagram` | Instagram top posts/reels | - ---- - -## Search (5 endpoints) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ✅ | GET | `/api/search` | Universal search | -| ✅ | GET | `/api/genres` | List genre IDs and names | -| ✅ | GET | `/api/genres/:id` | Get genre by ID | -| ⚠️ | GET | `/api/cities` | Get city info | -| ⚠️ | GET | `/api/search/social` | Social search | - ---- - -## Track (12 endpoints) - -| Status | Method | Endpoint | Description | -|--------|--------|----------|-------------| -| ✅ | GET | `/api/track/:id` | Track metadata | -| ✅ | GET | `/api/track/:id/milestones` | Track milestones | -| ✅ | GET | `/api/track/:id/topVideos` | Top TikTok videos | -| ✅ | GET | `/api/track/youtube/:id/topShorts` | Top YouTube Shorts | -| ✅ | GET | `/api/track/:id/video-trends` | TikTok video trends | -| ✅ | GET | `/api/track/:type/:id/get-ids` | Lookup track by platform ID | -| ✅ | GET | `/api/track/list/filter` | Filter/discover tracks | -| ⚠️ | GET | `/api/track/:id/:platform/:status/playlists` | Track playlists | -| ⚠️ | GET | `/api/track/:id/relatedTracks` | Related tracks | -| ⚠️ | GET | `/api/track/:id/:platform/stats/:mode` | Track stats | -| ⚠️ | GET | `/api/track/:id/:platform/playlists/snapshot` | Playlist snapshot | -| ⚠️ | GET | `/api/track/:id/:type/charts` | Track charts | - ---- - -## Summary - -| Category | Total | Working | Locked | Needs Params | -|----------|-------|---------|--------|--------------| -| Authorization | 1 | 1 | 0 | 0 | -| Album | 6 | 4 | 0 | 2 | -| Artist | 35 | 24 | 0 | 11 | -| Brand | 3 | 2 | 0 | 1 | -| Charts | 32 | 1 | 7 | 24 | -| City | 2 | 1 | 0 | 1 | -| Curator | 5 | 5 | 0 | 0 | -| Event | 1 | 1 | 0 | 0 | -| Festival | 1 | 1 | 0 | 0 | -| Genre | 1 | 1 | 0 | 0 | -| Playlist | 9 | 4 | 0 | 5 | -| Radio | 5 | 1 | 0 | 4 | -| Recommendation | 1 | 0 | 0 | 1 | -| SNS | 1 | 1 | 0 | 0 | -| Search | 5 | 3 | 0 | 2 | -| Track | 12 | 7 | 0 | 5 | -| **TOTAL** | **120** | **55** | **7** | **58** | diff --git a/skills/chart-metric/references/parameter-guide.md b/skills/chart-metric/references/parameter-guide.md deleted file mode 100644 index a24eafa..0000000 --- a/skills/chart-metric/references/parameter-guide.md +++ /dev/null @@ -1,470 +0,0 @@ -# Chartmetric API - Parameter Guide - -All parameters below have been **tested and verified working**. - ---- - -## Search Endpoints - -### Universal Search ✅ -``` -GET /api/search?q=Drake&type=artists -GET /api/search?q=One%20Dance&type=tracks -GET /api/search?q=Views&type=albums -``` -- `q`: Search query -- `type`: `artists` | `tracks` | `albums` - ---- - -## Artist Endpoints - -### Artist Metadata ✅ -``` -GET /api/artist/:id -``` -- No params required - -### Artist by Spotify ID ✅ -``` -GET /api/artist/spotify/:spotify_id/get-ids -``` -- Returns Chartmetric ID for the artist - -### Artist Stats ✅ -``` -GET /api/artist/:id/stat/:source -``` -- **Valid sources:** - - `spotify` ✅ - - `instagram` ✅ - - `tiktok` ✅ - - `twitter` ✅ - - `facebook` ✅ - - `youtube_channel` ✅ - - `youtube_artist` ✅ - - `soundcloud` ✅ - - `deezer` ✅ - - `twitch` ✅ - - `line` ✅ - - `melon` ✅ - - `wikipedia` ✅ - - `bandsintown` ✅ - - ~~`bilibili`~~ ❌ (doesn't exist) - - ~~`snap`~~ ❌ (doesn't exist) - -### Artist Where People Listen ✅ -``` -GET /api/artist/:id/where-people-listen -``` -- Returns top cities by Spotify monthly listeners - -### Artist URLs ✅ -``` -GET /api/artist/:id/urls -``` -- Returns social and streaming URLs - -### Artist Albums ✅ -``` -GET /api/artist/:id/albums -``` -- Returns all albums - -### Artist Tracks ✅ -``` -GET /api/artist/:id/tracks -``` -- Returns all tracks - -### Artist Top Tracks ✅ -``` -GET /api/artist/:id/top-tracks/tiktok -``` -- **Only `tiktok` works** -- ~~`cm`~~ and ~~`youtubeforartist`~~ return "Domain does not exist" - -### Artist Charts ✅ -``` -GET /api/artist/:id/:type/charts -``` -- **Working types:** - - `shazam` ✅ - - `itunes_top` ✅ - - `itunes_albums` ✅ - - `youtube` ✅ - - `youtube_tracks` ✅ - - `youtube_videos` ✅ - - `youtube_trends` ✅ - -- **NOT working (return "Streaming type does not exist"):** - - ~~`spotify_viral_daily`~~ - - ~~`spotify_top_daily`~~ - - ~~`applemusic_top`~~ - - ~~`beatport`~~ - - ~~`amazon`~~ - -### ANR by Playlists ✅ -``` -GET /api/artist/anr/by/playlists?sortBy=followers_total_reach_diff_week_percent -``` -- `sortBy` is REQUIRED -- DO NOT include `streamingType` (not allowed) - ---- - -## Track Endpoints - -### Track Metadata ✅ -``` -GET /api/track/:id -``` -- Use Chartmetric track ID (not Spotify ID) - -### Track by Spotify ID ✅ -``` -GET /api/track/spotify/:spotify_id/get-ids -``` -- Returns Chartmetric ID for the track - ---- - -## Album Endpoints - -### Album Metadata ✅ -``` -GET /api/album/:id -``` -- Use Chartmetric album ID - -### Album Tracks ✅ -``` -GET /api/album/:id/tracks -``` - -### Album by Spotify ID ✅ -``` -GET /api/album/spotify/:spotify_id/get-ids -``` -- Returns list with `cm_album` IDs - ---- - -## Chart Endpoints - -### Spotify Charts ✅ -``` -GET /api/charts/spotify?latest=true&country_code=US&interval=daily&type=plays -``` -| Param | Required | Values | -|-------|----------|--------| -| `country_code` | ✅ | `US`, `GB`, `DE`, etc. | -| `interval` | ✅ | `daily`, `weekly` | -| `type` | ✅ | `plays`, `popularity`, `playlist_count`, `playlist_reach`, `viral`, `regional` | -| `latest` | optional | `true` | -| `date` | optional | `2026-01-01` | - -### Apple Music Charts ✅ -``` -GET /api/charts/applemusic/tracks?latest=true&country_code=US&type=top -GET /api/charts/applemusic/albums?latest=true&country_code=US -``` -| Param | Required | Values | -|-------|----------|--------| -| `country_code` | ✅ | `US`, `GB`, etc. | -| `type` (tracks only) | ✅ | `top`, `daily`, `city` | - -### iTunes Charts ✅ -``` -GET /api/charts/itunes/tracks?latest=true&country_code=US -GET /api/charts/itunes/albums?latest=true&country_code=US -``` -| Param | Required | Values | -|-------|----------|--------| -| `country_code` | ✅ | `US`, `GB`, etc. | -| `genre` | optional | `pop`, `hip-hop`, etc. | - -### YouTube Charts ✅ -``` -GET /api/charts/youtube/videos?latest=true&country_code=US -``` -| Param | Required | Values | -|-------|----------|--------| -| `country_code` | ✅ | `US`, `GB`, etc. | - -### TikTok Charts ✅ -``` -GET /api/charts/tiktok/tracks?latest=true -``` -| Param | Required | Values | -|-------|----------|--------| -| `latest` | optional | `true` | -| ~~`country_code`~~ | ❌ | NOT ALLOWED | - -### Shazam Charts ✅ -``` -GET /api/charts/shazam?latest=true&country_code=US -``` - -### Deezer Charts ✅ -``` -GET /api/charts/deezer?latest=true&country_code=US -``` - -### Amazon Charts ✅ -``` -GET /api/charts/amazon/tracks?latest=true&genre=pop&type=popular_track -``` -| Param | Required | Values | -|-------|----------|--------| -| `type` | ✅ | `popular_track`, `new_track` | -| `genre` | ✅ | `pop`, `hip-hop`, `All Genres`, etc. | -| ~~`country_code`~~ | ❌ | NOT ALLOWED | - -### SoundCloud Charts ✅ -``` -GET /api/charts/soundcloud?latest=true&country_code=US&kind=top&genre=all-music -``` -| Param | Required | Values | -|-------|----------|--------| -| `country_code` | ✅ | `US`, `GLOBAL`, etc. | -| `kind` | ✅ | `top`, `trending` | -| `genre` | ✅ | `all-music`, specific genres | - -### Melon Charts (Korea) ✅ -``` -GET /api/charts/melon/track/general?duration=daily&genre=All%20Genres -``` -| Param | Required | Values | -|-------|----------|--------| -| `duration` | ✅ | `daily` | -| `genre` | ✅ | `All Genres`, `pop`, `k-pop`, `hot` | - -### Hanteo Charts (Korea) ✅ -``` -GET /api/charts/hanteo/album/music?duration=daily&latest=true -``` -| Param | Required | Values | -|-------|----------|--------| -| `duration` | ✅ | `daily` | -| Chart type in path | ✅ | `music` | - -### Chart Dates ✅ -``` -GET /api/charts/spotify_tracks/dates?fromDaysAgo=28 -``` -| Param | Required | Values | -|-------|----------|--------| -| `fromDaysAgo` | ✅ | Max 28 | - -### Chart Genres ✅ -``` -GET /api/charts/genres/apple_music -GET /api/charts/genres/shazam_genre -``` -- Valid platforms: `amazon`, `apple_music`, `beatport`, `itunes`, `shazam_genre`, `soundcloud`, etc. - ---- - -## Other Endpoints - -### Genre List ✅ -``` -GET /api/genre -``` -- No params required - ---- - -## Artist Playlists ✅ - -``` -GET /api/artist/:id/:platform/:status/playlists -``` - -### Path Parameters - -| Param | Required | Values | -|-------|----------|--------| -| `id` | ✅ | Chartmetric artist ID | -| `platform` | ✅ | `spotify`, `applemusic`, `deezer`, `amazon`, `youtube` | -| `status` | ✅ | `current`, `past` | - -### Query Parameters - -| Param | Type | Description | -|-------|------|-------------| -| `since` | date | Start date (YYYY-MM-DD) | -| `until` | date | End date (YYYY-MM-DD) | -| `limit` | integer | Results per page (default: 50) | -| `offset` | integer | Pagination offset | -| `sortColumn` | string | Sort by (see table below) | - -**Note:** API defaults to descending sort order. Use `--asc` flag in script for ascending. - -### Valid sortColumn by Platform & Status - -| Platform | Status | Valid sortColumn Values | -|----------|--------|------------------------| -| amazon | current | `added_at` (default), `countries`, `name`, `peak_position`, `track` | -| applemusic | current | `added_at` (default), `name`, `peak_position`, `position`, `track` | -| applemusic | past | `added_at`, `name`, `peak_position`, `position`, `removed_at` (default), `track` | -| deezer | current | `added_at` (default), `fdiff_month`, `followers`, `name`, `peak_position`, `track` | -| deezer | past | `added_at`, `fdiff_month`, `followers`, `name`, `peak_position`, `removed_at` (default), `track` | -| spotify | current | `added_at`, `code2`, `fdiff_month`, `followers` (default), `name`, `peak_position`, `position`, `track` | -| spotify | past | `added_at`, `code2`, `fdiff_month`, `followers` (default), `name`, `peak_position`, `position`, `removed_at`, `track` | -| youtube | current | `added_at` (default), `name`, `peak_position`, `track`, `vdiff_month`, `views` | -| youtube | past | `added_at`, `name`, `peak_position`, `removed_at` (default), `track`, `vdiff_month`, `views` | - -### Playlist Type Filters (Platform-Specific) - -| Filter | spotify | applemusic | deezer | amazon | -|--------|---------|------------|--------|--------| -| `editorial` | ✅ | ✅ | ✅ | | -| `editorialBrand` | | ✅ | | | -| `personalized` | ✅ | | | | -| `deezerPartner` | | | ✅ | | -| `chart` | ✅ | ✅ | ✅ | | -| `thisIs` | ✅ | | | | -| `hundredPercent` | | | ✅ | | -| `newMusicFriday` | ✅ | | | | -| `radio` | ✅ | ✅ | | | -| `fullyPersonalized` | ✅ | | | | -| `brand` | ✅ | | ✅ | | -| `majorCurator` | ✅ | | ✅ | | -| `musicBrand` | | ✅ | | | -| `nonMusicBrand` | | ✅ | | | -| `popularIndie` | ✅ | | ✅ | | -| `indie` | ✅ | ✅ | ✅ | | -| `audiobook` | ✅ | | | | -| `personalityArtist` | | ✅ | | | - -**⚠️ Important:** When no filter parameters are specified, the API may return an empty array. Specify filters explicitly to get data. - -### Example Requests - -```bash -# Basic - all current Spotify playlists -GET /api/artist/1022311/spotify/current/playlists - -# With date range and sorting -GET /api/artist/1022311/spotify/current/playlists?since=2025-01-01&sortColumn=followers&limit=50 - -# Only New Music Friday playlists -GET /api/artist/1022311/spotify/current/playlists?editorial=true&personalized=true&chart=false&thisIs=false&newMusicFriday=true&radio=false&fullyPersonalized=false&brand=true&majorCurator=true&popularIndie=true&indie=true&audiobook=false -``` - -**Note:** The generic `/artist/:id/playlists` returns 401 - use platform-specific path instead. - ---- - -## Related Artists ✅ - -### Basic Endpoint -``` -GET /api/artist/:id/relatedartists?limit=10 -``` - -| Param | Required | Description | -|-------|----------|-------------| -| `limit` | ✅ | Number of artists (1-100) | - -### Similar Artists by Configurations ✅ -``` -GET /api/artist/:id/similar-artists/by-configurations?audience=high&genre=high&limit=10 -``` - -| Param | Required | Values | Description | -|-------|----------|--------|-------------| -| `audience` | ⚠️ At least one config required | `high`, `medium`, `low` | Audience similarity | -| `mood` | ⚠️ At least one config required | `high`, `medium`, `low` | Mood similarity | -| `genre` | ⚠️ At least one config required | `high`, `medium`, `low` | Genre similarity | -| `musicality` | ⚠️ At least one config required | `high`, `medium`, `low` | Musicality similarity | -| `limit` | optional | integer | Number of results (default: 10) | -| `offset` | optional | integer | Pagination offset (default: 0) | - -**Response includes:** -- `similarity` score (0-1) -- `career_stage` (undiscovered, developing, mid-level, mainstream, superstar, legendary) -- `recent_momentum` (decline, gradual decline, steady, growth, explosive growth) -- Spotify followers, monthly listeners, playlist reach -- YouTube subscribers, TikTok followers, Instagram followers - -**Note:** `/artist/:id/similar` returns 401 - use `relatedartists` or `similar-artists/by-configurations` instead. - ---- - -## Curator Endpoints ✅ - -``` -GET /api/curator/:platform/:numeric_id -``` - -| Param | Values | -|-------|--------| -| `platform` | `spotify` | -| `numeric_id` | Chartmetric curator ID (integer) | - -Example: `/api/curator/spotify/1` returns PlayStation curator - -**Note:** `/curator/search` returns 401 - must have numeric curator ID. - ---- - -## Endpoints That Return 401 (Internal Only) - -These specific paths are internal and not accessible: - -1. `/api/charts/` - Charts Introduction (use specific chart endpoints) -2. `/api/artist/:id/playlists` - Use `/artist/:id/:platform/current/playlists` -3. `/api/artist/:id/similar` - Use `/artist/:id/relatedartists?limit=N` -4. `/api/curator/search` - Use `/curator/:platform/:id` with numeric ID -5. `/api/charts/tiktok/popular` - Use `/charts/tiktok/tracks` -6. `/api/charts/tiktok/viral` - Use `/charts/tiktok/tracks` - ---- - -## Summary - -**Verified Working: 57+ core endpoints** - -All essential functionality is available: -- ✅ Search (artists, tracks, albums) -- ✅ Artist data (metadata, stats, cities, URLs, albums, tracks, career) -- ✅ Artist playlists (per platform: spotify, applemusic, deezer, amazon, youtube) - - With filter support: editorial, newMusicFriday, indie, chart, etc. -- ✅ Related artists (basic endpoint) -- ✅ Similar artists by configurations (with audience/mood/genre/musicality filters) -- ✅ Platform metrics (14 sources) -- ✅ Track/album lookup by Spotify ID -- ✅ Curator lookup (by numeric ID) -- ✅ Charts (Spotify, Apple Music, iTunes, YouTube, TikTok, Shazam, Deezer, Amazon, SoundCloud, Melon, Hanteo) -- ✅ Genre discovery -- ✅ ANR playlists - ---- - -## Quick Reference: Most Used Endpoints - -```bash -# Search -GET /api/search?q=Drake&type=artists - -# Artist profile -GET /api/artist/1320 - -# Artist by Spotify -GET /api/artist/spotify/3TVXtAsR1Inumwj472S9r4/get-ids - -# Artist stats -GET /api/artist/1320/stat/spotify - -# Artist cities -GET /api/artist/1320/where-people-listen - -# Spotify charts -GET /api/charts/spotify?latest=true&country_code=US&interval=daily&type=plays - -# TikTok charts -GET /api/charts/tiktok/tracks?latest=true -``` diff --git a/skills/chart-metric/scripts/discover_artists.py b/skills/chart-metric/scripts/discover_artists.py deleted file mode 100644 index 1c6737d..0000000 --- a/skills/chart-metric/scripts/discover_artists.py +++ /dev/null @@ -1,262 +0,0 @@ -#!/usr/bin/env python3 -""" -Discover artists by filtering on metrics, geography, genre, and more. - -This is the POWER endpoint - find artists matching specific criteria. - -Usage: - # Find US artists with 100K-500K Spotify monthly listeners - python discover_artists.py --country US --spotify-listeners 100000 500000 - - # Find TikTok-famous artists weak on Spotify (signing opportunity) - python discover_artists.py --tiktok-followers 1000000 10000000 --spotify-listeners 0 100000 - - # Find emerging hip-hop artists - python discover_artists.py --genre 86 --spotify-listeners 50000 200000 --sort weekly_diff.sp_monthly_listeners - - # Find solo female artists in Brazil - python discover_artists.py --country BR --band false --pronoun she/her - - # Find artists at a specific festival - python discover_artists.py --festival-id 123 - -Metric Filters (use two values for [min, max]): - --spotify-listeners Spotify monthly listeners range - --spotify-followers Spotify followers range - --spotify-popularity Spotify popularity (0-100) - --tiktok-followers TikTok followers range - --tiktok-likes TikTok likes range - --instagram-followers Instagram followers range - --youtube-subscribers YouTube subscribers range - --cpp Cross-Platform Performance score range - -Other Filters: - --country 2-letter country code (US, GB, BR, etc.) - --genre Genre ID (use list_genres.py to find IDs) - --subgenre Subgenre ID - --band true/false - filter bands or solo artists - --pronoun he/him, she/her, they/them, any - --first-release-days Artists who debuted within X days - --festival-id Artists playing at specific festival - -Sorting: - --sort Sort column (default: latest.sp_monthly_listeners) - --asc Sort ascending (default: descending) - -Sort column format: . - Periods: latest, weekly_diff, monthly_diff - Stats: sp_monthly_listeners, sp_followers, tt_followers, ig_followers, cpp, etc. - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import json -import argparse -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def discover_artists( - country: str = None, - genre_id: int = None, - subgenre_id: int = None, - band: bool = None, - pronoun: str = None, - first_release_days: int = None, - festival_ids: list = None, - spotify_listeners: tuple = None, - spotify_followers: tuple = None, - spotify_popularity: tuple = None, - tiktok_followers: tuple = None, - tiktok_likes: tuple = None, - instagram_followers: tuple = None, - youtube_subscribers: tuple = None, - cpp: tuple = None, - sort_column: str = "latest.sp_monthly_listeners", - sort_desc: bool = True, - limit: int = 50, - offset: int = 0 -) -> dict: - """ - Discover artists using Chartmetric's powerful filter endpoint. - - Returns artists matching the specified criteria with all their metrics. - """ - token = get_token() - - params = { - "limit": limit, - "offset": offset, - "sortColumn": sort_column, - "sortOrderDesc": str(sort_desc).lower() - } - - # Basic filters - if country: - params["code2"] = country - if genre_id: - params["tagId"] = genre_id - if subgenre_id: - params["subTagId"] = subgenre_id - if band is not None: - params["band"] = str(band).lower() - if pronoun: - params["pronoun"] = pronoun - if first_release_days: - params["firstReleaseDaysAgo"] = first_release_days - if festival_ids: - params["eventIds[]"] = festival_ids - - # Metric range filters (need special handling for arrays) - range_params = [] - if spotify_listeners: - range_params.append(("sp_ml[]", spotify_listeners[0])) - range_params.append(("sp_ml[]", spotify_listeners[1])) - if spotify_followers: - range_params.append(("sp_f[]", spotify_followers[0])) - range_params.append(("sp_f[]", spotify_followers[1])) - if spotify_popularity: - range_params.append(("sp_p[]", spotify_popularity[0])) - range_params.append(("sp_p[]", spotify_popularity[1])) - if tiktok_followers: - range_params.append(("tt_f[]", tiktok_followers[0])) - range_params.append(("tt_f[]", tiktok_followers[1])) - if tiktok_likes: - range_params.append(("tt_l[]", tiktok_likes[0])) - range_params.append(("tt_l[]", tiktok_likes[1])) - if instagram_followers: - range_params.append(("ig_f[]", instagram_followers[0])) - range_params.append(("ig_f[]", instagram_followers[1])) - if youtube_subscribers: - range_params.append(("ytc_s[]", youtube_subscribers[0])) - range_params.append(("ytc_s[]", youtube_subscribers[1])) - if cpp: - range_params.append(("cpp[]", cpp[0])) - range_params.append(("cpp[]", cpp[1])) - - url = f"{API_BASE}/artist/list/filter" - - combined_params = list(params.items()) + range_params if range_params else params - response = requests.get( - url, - headers={"Authorization": f"Bearer {token}"}, - params=combined_params, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 401: - return {"error": "Unauthorized", "message": "This endpoint may require a higher subscription tier."} - - response.raise_for_status() - return response.json() - - -def main(): - parser = argparse.ArgumentParser( - description="Discover artists by filtering on metrics and attributes", - formatter_class=argparse.RawDescriptionHelpFormatter - ) - - # Basic filters - parser.add_argument("--country", "-c", help="2-letter country code (US, GB, BR)") - parser.add_argument("--genre", "-g", type=int, help="Genre ID") - parser.add_argument("--subgenre", type=int, help="Subgenre ID") - parser.add_argument("--band", choices=["true", "false"], help="Filter bands or solo") - parser.add_argument("--pronoun", choices=["any", "he/him", "she/her", "they/them"]) - parser.add_argument("--first-release-days", type=int, help="Debuted within X days") - parser.add_argument("--festival-id", type=int, action="append", help="Festival event ID") - - # Metric ranges (2 values each) - parser.add_argument("--spotify-listeners", type=int, nargs=2, metavar=("MIN", "MAX"), - help="Spotify monthly listeners range") - parser.add_argument("--spotify-followers", type=int, nargs=2, metavar=("MIN", "MAX"), - help="Spotify followers range") - parser.add_argument("--spotify-popularity", type=int, nargs=2, metavar=("MIN", "MAX"), - help="Spotify popularity range (0-100)") - parser.add_argument("--tiktok-followers", type=int, nargs=2, metavar=("MIN", "MAX"), - help="TikTok followers range") - parser.add_argument("--tiktok-likes", type=int, nargs=2, metavar=("MIN", "MAX"), - help="TikTok likes range") - parser.add_argument("--instagram-followers", type=int, nargs=2, metavar=("MIN", "MAX"), - help="Instagram followers range") - parser.add_argument("--youtube-subscribers", type=int, nargs=2, metavar=("MIN", "MAX"), - help="YouTube subscribers range") - parser.add_argument("--cpp", type=int, nargs=2, metavar=("MIN", "MAX"), - help="Cross-Platform Performance score range") - - # Sorting and pagination - parser.add_argument("--sort", default="latest.sp_monthly_listeners", - help="Sort column (default: latest.sp_monthly_listeners)") - parser.add_argument("--asc", action="store_true", help="Sort ascending") - parser.add_argument("--limit", "-l", type=int, default=50, help="Results limit") - parser.add_argument("--offset", type=int, default=0, help="Pagination offset") - parser.add_argument("--json", action="store_true", help="Output raw JSON") - - args = parser.parse_args() - - result = discover_artists( - country=args.country, - genre_id=args.genre, - subgenre_id=args.subgenre, - band=args.band == "true" if args.band else None, - pronoun=args.pronoun, - first_release_days=args.first_release_days, - festival_ids=args.festival_id, - spotify_listeners=tuple(args.spotify_listeners) if args.spotify_listeners else None, - spotify_followers=tuple(args.spotify_followers) if args.spotify_followers else None, - spotify_popularity=tuple(args.spotify_popularity) if args.spotify_popularity else None, - tiktok_followers=tuple(args.tiktok_followers) if args.tiktok_followers else None, - tiktok_likes=tuple(args.tiktok_likes) if args.tiktok_likes else None, - instagram_followers=tuple(args.instagram_followers) if args.instagram_followers else None, - youtube_subscribers=tuple(args.youtube_subscribers) if args.youtube_subscribers else None, - cpp=tuple(args.cpp) if args.cpp else None, - sort_column=args.sort, - sort_desc=not args.asc, - limit=args.limit, - offset=args.offset - ) - - if args.json: - print(json.dumps(result, indent=2)) - return - - if "error" in result: - print(f"Error: {result.get('error')}") - if "message" in result: - print(result.get('message')) - sys.exit(1) - - artists = result.get("obj", []) - print(f"Found {len(artists)} artists:\n") - - for artist in artists[:30]: - print(f"- {artist.get('name')}") - print(f" CM ID: {artist.get('id')}") - - # Show key metrics - if artist.get('sp_monthly_listeners'): - print(f" Spotify Listeners: {artist.get('sp_monthly_listeners'):,}") - if artist.get('sp_followers'): - print(f" Spotify Followers: {artist.get('sp_followers'):,}") - if artist.get('tiktok_followers'): - print(f" TikTok Followers: {artist.get('tiktok_followers'):,}") - if artist.get('ins_followers'): - print(f" Instagram Followers: {artist.get('ins_followers'):,}") - if artist.get('code2'): - print(f" Country: {artist.get('code2')}") - - print() - - if len(artists) > 30: - print(f"... and {len(artists) - 30} more (use --limit to adjust)") - - -if __name__ == "__main__": - main() diff --git a/skills/chart-metric/scripts/get_artist.py b/skills/chart-metric/scripts/get_artist.py deleted file mode 100644 index cbed7df..0000000 --- a/skills/chart-metric/scripts/get_artist.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python3 -""" -Get artist profile by Chartmetric ID. - -Usage: - python get_artist.py - python get_artist.py 1234567 - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import json -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def get_artist(cm_id: str) -> dict: - """Fetch artist profile from Chartmetric.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/artist/{cm_id}", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - if response.status_code == 402: - return { - "error": "Payment Required", - "message": "Your Chartmetric API subscription may be expired or this endpoint requires a higher tier. Check your account at chartmetric.com or contact hi@chartmetric.com" - } - - if response.status_code == 404: - return {"error": "Artist not found", "chartmetric_id": cm_id} - - response.raise_for_status() - return response.json() - - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python get_artist.py ") - sys.exit(1) - - result = get_artist(sys.argv[1]) - print(json.dumps(result, indent=2)) - if "error" in result: - sys.exit(1) diff --git a/skills/chart-metric/scripts/get_artist_albums.py b/skills/chart-metric/scripts/get_artist_albums.py deleted file mode 100644 index 6f6b324..0000000 --- a/skills/chart-metric/scripts/get_artist_albums.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -""" -Get artist's albums. - -Usage: - python get_artist_albums.py - python get_artist_albums.py 3380 - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import json -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def get_artist_albums(cm_id: str) -> dict: - """Fetch artist's albums from Chartmetric.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/artist/{cm_id}/albums", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 404: - return {"error": "Artist not found", "chartmetric_id": cm_id} - - response.raise_for_status() - return response.json() - - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python get_artist_albums.py ") - sys.exit(1) - - result = get_artist_albums(sys.argv[1]) - - if "error" in result: - print(f"Error: {result.get('error')}") - if "message" in result: - print(result.get('message')) - sys.exit(1) - - albums = result.get("obj", []) - print(f"Found {len(albums)} albums:\n") - for album in albums[:20]: # Limit to first 20 - print(f"- {album.get('name')}") - print(f" Chartmetric ID: {album.get('id')}") - print(f" Release: {album.get('release_date', 'N/A')}") - print() diff --git a/skills/chart-metric/scripts/get_artist_audience.py b/skills/chart-metric/scripts/get_artist_audience.py deleted file mode 100644 index 73c96ca..0000000 --- a/skills/chart-metric/scripts/get_artist_audience.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 -""" -Get artist audience demographics for a platform. - -Usage: - python get_artist_audience.py [--platform instagram] - python get_artist_audience.py 3380 - python get_artist_audience.py 3380 --platform tiktok - python get_artist_audience.py 3380 --platform youtube - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import json -import argparse -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def get_artist_audience(cm_id: str, platform: str = "instagram") -> dict: - """Fetch artist audience demographics from Chartmetric.""" - token = get_token() - - endpoint_map = { - "instagram": f"{API_BASE}/artist/{cm_id}/instagram-audience-stats", - "tiktok": f"{API_BASE}/artist/{cm_id}/tiktok-audience-stats", - "youtube": f"{API_BASE}/artist/{cm_id}/youtube-audience-stats", - } - - url = endpoint_map.get(platform) - if not url: - return {"error": f"Unknown platform: {platform}", "valid_platforms": list(endpoint_map.keys())} - - response = requests.get(url, headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 404: - return {"error": "Artist not found or no audience data", "chartmetric_id": cm_id} - - response.raise_for_status() - return response.json() - - -def main(): - parser = argparse.ArgumentParser(description="Get artist audience demographics") - parser.add_argument("chartmetric_id", help="Chartmetric artist ID") - parser.add_argument("--platform", "-p", default="instagram", - choices=["instagram", "tiktok", "youtube"], - help="Platform for audience data") - - args = parser.parse_args() - result = get_artist_audience(args.chartmetric_id, args.platform) - - if "error" in result: - print(f"Error: {result.get('error')}") - if "message" in result: - print(result.get('message')) - sys.exit(1) - - print(json.dumps(result, indent=2)) - - -if __name__ == "__main__": - main() diff --git a/skills/chart-metric/scripts/get_artist_by_spotify.py b/skills/chart-metric/scripts/get_artist_by_spotify.py deleted file mode 100644 index c9d7c9b..0000000 --- a/skills/chart-metric/scripts/get_artist_by_spotify.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python3 -""" -Lookup artist by Spotify ID or URL. - -Usage: - python get_artist_by_spotify.py - python get_artist_by_spotify.py 3TVXtAsR1Inumwj472S9r4 - python get_artist_by_spotify.py "https://open.spotify.com/artist/3TVXtAsR1Inumwj472S9r4" - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import json -import re -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def extract_spotify_id(url_or_id: str) -> str: - """Extract Spotify artist ID from URL or return as-is if already an ID.""" - # If it looks like a URL, extract the ID - match = re.search(r"artist[/:]([a-zA-Z0-9]+)", url_or_id) - if match: - return match.group(1) - return url_or_id - - -def get_artist_by_spotify(spotify_id: str) -> dict: - """Lookup Chartmetric artist by Spotify ID. - - Uses the /artist/:type/:id/get-ids endpoint to lookup artist by platform ID. - Returns the Chartmetric artist ID and other platform IDs. - """ - token = get_token() - - response = requests.get( - f"{API_BASE}/artist/spotify/{spotify_id}/get-ids", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - if response.status_code == 402: - return { - "error": "Payment Required", - "message": "Your Chartmetric API subscription may be expired or this endpoint requires a higher tier. Check your account at chartmetric.com or contact hi@chartmetric.com" - } - - if response.status_code == 404: - return {"error": "Artist not found in Chartmetric", "spotify_id": spotify_id} - - response.raise_for_status() - return response.json() - - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python get_artist_by_spotify.py ") - sys.exit(1) - - spotify_id = extract_spotify_id(sys.argv[1]) - result = get_artist_by_spotify(spotify_id) - print(json.dumps(result, indent=2)) - if "error" in result: - sys.exit(1) diff --git a/skills/chart-metric/scripts/get_artist_career.py b/skills/chart-metric/scripts/get_artist_career.py deleted file mode 100644 index c0e7c88..0000000 --- a/skills/chart-metric/scripts/get_artist_career.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 -""" -Get artist career history. - -Usage: - python get_artist_career.py - python get_artist_career.py 3380 - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import json -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def get_artist_career(cm_id: str) -> dict: - """Fetch artist career history from Chartmetric.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/artist/{cm_id}/career", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 404: - return {"error": "Artist not found", "chartmetric_id": cm_id} - - response.raise_for_status() - return response.json() - - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python get_artist_career.py ") - sys.exit(1) - - result = get_artist_career(sys.argv[1]) - print(json.dumps(result, indent=2)) - if "error" in result: - sys.exit(1) diff --git a/skills/chart-metric/scripts/get_artist_cities.py b/skills/chart-metric/scripts/get_artist_cities.py deleted file mode 100644 index 6f31ad9..0000000 --- a/skills/chart-metric/scripts/get_artist_cities.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python3 -""" -Get cities where people listen to an artist (Spotify data). - -Usage: - python get_artist_cities.py - python get_artist_cities.py 3380 - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import json -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def get_artist_cities(cm_id: str) -> dict: - """Fetch where people listen data from Chartmetric.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/artist/{cm_id}/where-people-listen", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 404: - return {"error": "Artist not found", "chartmetric_id": cm_id} - - response.raise_for_status() - return response.json() - - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python get_artist_cities.py ") - sys.exit(1) - - result = get_artist_cities(sys.argv[1]) - - if "error" in result: - print(f"Error: {result.get('error')}") - if "message" in result: - print(result.get('message')) - sys.exit(1) - - cities_data = result.get("obj", {}).get("cities", {}) - print(f"Top cities for this artist:\n") - - # Get latest listener count for each city - city_latest = [] - for city_name, history in cities_data.items(): - if history and len(history) > 0: - latest = history[-1] # Most recent data point - city_latest.append({ - "name": city_name, - "country": latest.get("code2", ""), - "listeners": latest.get("listeners", 0) - }) - - # Sort by listeners descending - city_latest.sort(key=lambda x: x.get("listeners", 0), reverse=True) - - for city in city_latest[:20]: - print(f"- {city.get('name', 'Unknown')}, {city.get('country', '')}") - print(f" Listeners: {city.get('listeners', 0):,}") - print() diff --git a/skills/chart-metric/scripts/get_artist_insights.py b/skills/chart-metric/scripts/get_artist_insights.py deleted file mode 100644 index 8e37d91..0000000 --- a/skills/chart-metric/scripts/get_artist_insights.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -""" -Get AI-generated noteworthy insights for an artist. - -Usage: - python get_artist_insights.py - python get_artist_insights.py 3380 - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import json -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def get_artist_insights(cm_id: str) -> dict: - """Fetch noteworthy insights from Chartmetric.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/artist/{cm_id}/noteworthy-insights", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 404: - return {"error": "Artist not found", "chartmetric_id": cm_id} - - response.raise_for_status() - return response.json() - - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python get_artist_insights.py ") - sys.exit(1) - - result = get_artist_insights(sys.argv[1]) - - if "error" in result: - print(f"Error: {result.get('error')}") - if "message" in result: - print(result.get('message')) - sys.exit(1) - - insights = result.get("obj", []) - print(f"Noteworthy Insights:\n") - for insight in insights: - print(f"• {insight.get('text', insight)}") - print() diff --git a/skills/chart-metric/scripts/get_artist_instagram_posts.py b/skills/chart-metric/scripts/get_artist_instagram_posts.py deleted file mode 100644 index 836cf65..0000000 --- a/skills/chart-metric/scripts/get_artist_instagram_posts.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python3 -""" -Get artist's top Instagram posts and reels. - -Usage: - python get_artist_instagram_posts.py - python get_artist_instagram_posts.py 3380 - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import json -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def get_artist_instagram_posts(cm_id: str) -> dict: - """Fetch artist's top Instagram posts and reels.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/SNS/deepSocial/cm_artist/{cm_id}/instagram", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 404: - return {"error": "Artist not found or no Instagram data", "chartmetric_id": cm_id} - - response.raise_for_status() - return response.json() - - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python get_artist_instagram_posts.py ") - sys.exit(1) - - result = get_artist_instagram_posts(sys.argv[1]) - - if "error" in result: - print(f"Error: {result.get('error')}") - if "message" in result: - print(result.get('message')) - sys.exit(1) - - print(json.dumps(result, indent=2)) diff --git a/skills/chart-metric/scripts/get_artist_metrics.py b/skills/chart-metric/scripts/get_artist_metrics.py deleted file mode 100644 index 09d5632..0000000 --- a/skills/chart-metric/scripts/get_artist_metrics.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python3 -""" -Get artist metrics from a specific platform. - -Usage: - python get_artist_metrics.py --source spotify - python get_artist_metrics.py 3380 -s youtube_channel - python get_artist_metrics.py 3380 -s instagram - -Platforms (14 total): - spotify, instagram, tiktok, twitter, facebook, youtube_channel, - youtube_artist, soundcloud, deezer, twitch, line, melon, - wikipedia, bandsintown - -Note: Use 'youtube_channel' or 'youtube_artist', NOT 'youtube' - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import json -import argparse -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - -VALID_SOURCES = [ - "spotify", - "instagram", - "tiktok", - "twitter", - "facebook", - "youtube_channel", - "youtube_artist", - "soundcloud", - "deezer", - "twitch", - "line", - "melon", - "wikipedia", - "bandsintown", -] - - -def get_artist_metrics(cm_id: str, source: str) -> dict: - """Fetch artist metrics for a specific platform.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/artist/{cm_id}/stat/{source}", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - if response.status_code == 402: - return { - "error": "Payment Required", - "message": "Your Chartmetric API subscription may be expired or this endpoint requires a higher tier. Check your account at chartmetric.com or contact hi@chartmetric.com" - } - - if response.status_code == 404: - return {"error": "Artist not found", "chartmetric_id": cm_id} - - response.raise_for_status() - return response.json() - - -def main(): - parser = argparse.ArgumentParser(description="Get artist metrics for a platform") - parser.add_argument("chartmetric_id", help="Chartmetric artist ID") - parser.add_argument( - "--source", "-s", - required=True, - choices=VALID_SOURCES, - help="Platform source" - ) - - args = parser.parse_args() - result = get_artist_metrics(args.chartmetric_id, args.source) - print(json.dumps(result, indent=2)) - if "error" in result: - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/skills/chart-metric/scripts/get_artist_playlists.py b/skills/chart-metric/scripts/get_artist_playlists.py deleted file mode 100644 index 65ddb85..0000000 --- a/skills/chart-metric/scripts/get_artist_playlists.py +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/env python3 -""" -Get artist playlist placements. - -Usage: - # Basic usage - python get_artist_playlists.py - python get_artist_playlists.py 3380 --platform spotify --status current - python get_artist_playlists.py 3380 --platform applemusic --status past - - # With filters (Spotify) - python get_artist_playlists.py 3380 --editorial --newMusicFriday - python get_artist_playlists.py 3380 --chart --indie - - # With date range and sorting - python get_artist_playlists.py 3380 --since 2025-01-01 --sort followers --limit 50 - -Platforms: spotify, applemusic, deezer, amazon, youtube -Status: current, past - -Spotify filters: editorial, personalized, chart, thisIs, newMusicFriday, radio, - fullyPersonalized, brand, majorCurator, popularIndie, indie, audiobook - -Apple Music filters: editorial, editorialBrand, chart, radio, musicBrand, - nonMusicBrand, indie, personalityArtist - -Deezer filters: editorial, deezerPartner, chart, hundredPercent, brand, - majorCurator, popularIndie, indie - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import argparse -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - -# Platform-specific filter support -PLATFORM_FILTERS = { - "spotify": ["editorial", "personalized", "chart", "thisIs", "newMusicFriday", - "radio", "fullyPersonalized", "brand", "majorCurator", - "popularIndie", "indie", "audiobook"], - "applemusic": ["editorial", "editorialBrand", "chart", "radio", "musicBrand", - "nonMusicBrand", "indie", "personalityArtist"], - "deezer": ["editorial", "deezerPartner", "chart", "hundredPercent", "brand", - "majorCurator", "popularIndie", "indie"], - "amazon": [], # No filters for Amazon - "youtube": [] # No filters for YouTube -} - - -def get_artist_playlists( - cm_id: str, - platform: str = "spotify", - status: str = "current", - since: str = None, - until: str = None, - limit: int = 50, - offset: int = 0, - sort_column: str = None, - sort_desc: bool = True, - filters: dict = None -) -> dict: - """ - Fetch artist playlist placements from Chartmetric. - - Args: - cm_id: Chartmetric artist ID - platform: spotify, applemusic, deezer, amazon, youtube - status: current or past - since: Start date (YYYY-MM-DD) - until: End date (YYYY-MM-DD) - limit: Results per page - offset: Pagination offset - sort_column: Column to sort by - sort_desc: Sort descending - filters: Dict of boolean filters (editorial, indie, etc.) - """ - token = get_token() - - params = {"limit": limit} - - if offset > 0: - params["offset"] = offset - if since: - params["since"] = since - if until: - params["until"] = until - if sort_column: - params["sortColumn"] = sort_column - # Only include sortOrderDesc when explicitly sorting - if not sort_desc: # API defaults to descending, so only set when ascending - params["sortOrderDesc"] = "false" - - # Add filter parameters - if filters: - for key, value in filters.items(): - if key in PLATFORM_FILTERS.get(platform, []): - params[key] = str(value).lower() - - response = requests.get( - f"{API_BASE}/artist/{cm_id}/{platform}/{status}/playlists", - headers={"Authorization": f"Bearer {token}"}, - params=params, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 401: - return {"error": "Unauthorized", "message": "This endpoint may require a higher subscription tier."} - - if response.status_code == 404: - return {"error": "Artist not found", "chartmetric_id": cm_id} - - response.raise_for_status() - return response.json() - - -def main(): - parser = argparse.ArgumentParser( - description="Get artist playlist placements", - formatter_class=argparse.RawDescriptionHelpFormatter - ) - parser.add_argument("chartmetric_id", help="Chartmetric artist ID") - parser.add_argument("--platform", "-p", default="spotify", - choices=["spotify", "applemusic", "deezer", "amazon", "youtube"], - help="Playlist platform (default: spotify)") - parser.add_argument("--status", "-s", default="current", - choices=["current", "past"], - help="Current or past placements (default: current)") - - # Date and pagination - parser.add_argument("--since", help="Start date (YYYY-MM-DD)") - parser.add_argument("--until", help="End date (YYYY-MM-DD)") - parser.add_argument("--limit", "-l", type=int, default=50, help="Results limit") - parser.add_argument("--offset", type=int, default=0, help="Pagination offset") - parser.add_argument("--sort", help="Sort column (followers, added_at, name, etc.)") - parser.add_argument("--asc", action="store_true", help="Sort ascending (default: descending)") - - # Filter flags (all platforms) - parser.add_argument("--editorial", action="store_true", help="Editorial playlists") - parser.add_argument("--chart", action="store_true", help="Chart playlists") - parser.add_argument("--indie", action="store_true", help="Indie curator playlists") - parser.add_argument("--radio", action="store_true", help="Radio playlists") - parser.add_argument("--brand", action="store_true", help="Brand playlists") - parser.add_argument("--majorCurator", action="store_true", help="Major curator playlists") - parser.add_argument("--popularIndie", action="store_true", help="Popular indie playlists") - - # Spotify-specific filters - parser.add_argument("--personalized", action="store_true", help="Personalized (Spotify)") - parser.add_argument("--thisIs", action="store_true", help="This Is playlists (Spotify)") - parser.add_argument("--newMusicFriday", action="store_true", help="New Music Friday (Spotify)") - parser.add_argument("--fullyPersonalized", action="store_true", help="Fully personalized (Spotify)") - parser.add_argument("--audiobook", action="store_true", help="Audiobook playlists (Spotify)") - - # Apple Music filters - parser.add_argument("--editorialBrand", action="store_true", help="Editorial brand (Apple)") - parser.add_argument("--musicBrand", action="store_true", help="Music brand (Apple)") - parser.add_argument("--nonMusicBrand", action="store_true", help="Non-music brand (Apple)") - parser.add_argument("--personalityArtist", action="store_true", help="Personality/artist (Apple)") - - # Deezer filters - parser.add_argument("--deezerPartner", action="store_true", help="Deezer partner") - parser.add_argument("--hundredPercent", action="store_true", help="100% playlists (Deezer)") - - args = parser.parse_args() - - # Build filters dict from args - filter_names = ["editorial", "chart", "indie", "radio", "brand", "majorCurator", - "popularIndie", "personalized", "thisIs", "newMusicFriday", - "fullyPersonalized", "audiobook", "editorialBrand", "musicBrand", - "nonMusicBrand", "personalityArtist", "deezerPartner", "hundredPercent"] - - filters = {} - for name in filter_names: - if getattr(args, name, False): - filters[name] = True - - result = get_artist_playlists( - args.chartmetric_id, - platform=args.platform, - status=args.status, - since=args.since, - until=args.until, - limit=args.limit, - offset=args.offset, - sort_column=args.sort, - sort_desc=not args.asc, - filters=filters if filters else None - ) - - if "error" in result: - print(f"Error: {result.get('error')}") - if "message" in result: - print(result.get('message')) - sys.exit(1) - - placements = result.get("obj", []) - print(f"Found {len(placements)} playlist placements:\n") - - for item in placements[:20]: - pl = item.get("playlist", {}) - track = item.get("track", {}) - print(f"- {pl.get('name', 'Unknown')}") - print(f" Track: {track.get('name', 'Unknown')}") - - position = pl.get('position', 'N/A') - peak = pl.get('peak_position', 'N/A') - print(f" Position: {position} (Peak: {peak})") - - followers = pl.get('followers') - if followers: - print(f" Followers: {followers:,}") - - print(f" Added: {pl.get('added_at', 'N/A')}") - - if args.status == 'past' and pl.get('removed_at'): - print(f" Removed: {pl.get('removed_at')}") - - curator = pl.get('curator_name', 'Unknown') - print(f" Owner: {curator}") - print() - - if len(placements) > 20: - print(f"... and {len(placements) - 20} more (use --limit to adjust)") - - -if __name__ == "__main__": - main() diff --git a/skills/chart-metric/scripts/get_artist_tracks.py b/skills/chart-metric/scripts/get_artist_tracks.py deleted file mode 100644 index ffa4b2c..0000000 --- a/skills/chart-metric/scripts/get_artist_tracks.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -""" -Get artist's tracks. - -Usage: - python get_artist_tracks.py - python get_artist_tracks.py 3380 - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import json -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def get_artist_tracks(cm_id: str) -> dict: - """Fetch artist's tracks from Chartmetric.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/artist/{cm_id}/tracks", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 404: - return {"error": "Artist not found", "chartmetric_id": cm_id} - - response.raise_for_status() - return response.json() - - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python get_artist_tracks.py ") - sys.exit(1) - - result = get_artist_tracks(sys.argv[1]) - - if "error" in result: - print(f"Error: {result.get('error')}") - if "message" in result: - print(result.get('message')) - sys.exit(1) - - tracks = result.get("obj", []) - print(f"Found {len(tracks)} tracks:\n") - for track in tracks[:20]: # Limit to first 20 - print(f"- {track.get('name')}") - print(f" Chartmetric ID: {track.get('id')}") - print(f" Album: {track.get('album_names', ['N/A'])[0] if track.get('album_names') else 'N/A'}") - print() diff --git a/skills/chart-metric/scripts/get_artist_urls.py b/skills/chart-metric/scripts/get_artist_urls.py deleted file mode 100644 index eb25b80..0000000 --- a/skills/chart-metric/scripts/get_artist_urls.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -""" -Get artist's social and streaming service URLs. - -Usage: - python get_artist_urls.py - python get_artist_urls.py 3380 - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import json -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def get_artist_urls(cm_id: str) -> dict: - """Fetch artist's social/streaming URLs from Chartmetric.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/artist/{cm_id}/urls", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 404: - return {"error": "Artist not found", "chartmetric_id": cm_id} - - response.raise_for_status() - return response.json() - - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python get_artist_urls.py ") - sys.exit(1) - - result = get_artist_urls(sys.argv[1]) - - if "error" in result: - print(f"Error: {result.get('error')}") - if "message" in result: - print(result.get('message')) - sys.exit(1) - - urls = result.get("obj", []) - print("Artist URLs:\n") - - # Handle both list and dict formats - if isinstance(urls, list): - for item in urls: - if isinstance(item, dict): - domain = item.get("domain", "unknown") - url = item.get("url", "") - if url: - print(f"- {domain}: {url}") - else: - print(f"- {item}") - elif isinstance(urls, dict): - for platform, url in urls.items(): - if url: - print(f"- {platform}: {url}") diff --git a/skills/chart-metric/scripts/get_curator.py b/skills/chart-metric/scripts/get_curator.py deleted file mode 100644 index ad65a73..0000000 --- a/skills/chart-metric/scripts/get_curator.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python3 -""" -Get curator/playlist owner information. - -Usage: - python get_curator.py [--platform spotify] - python get_curator.py 1 - python get_curator.py 1 --platform spotify - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import argparse -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def get_curator(curator_id: str, platform: str = "spotify") -> dict: - """Fetch curator info from Chartmetric.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/curator/{platform}/{curator_id}", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 401: - return {"error": "Unauthorized", "message": "This endpoint may require a higher subscription tier."} - - if response.status_code == 400: - return {"error": "Bad Request", "message": "Curator ID must be a numeric Chartmetric ID."} - - if response.status_code == 404: - return {"error": "Curator not found", "curator_id": curator_id} - - response.raise_for_status() - return response.json() - - -def main(): - parser = argparse.ArgumentParser(description="Get curator information") - parser.add_argument("curator_id", help="Chartmetric curator ID (numeric)") - parser.add_argument("--platform", "-p", default="spotify", - choices=["spotify"], - help="Curator platform (currently only spotify supported)") - - args = parser.parse_args() - result = get_curator(args.curator_id, args.platform) - - if "error" in result: - print(f"Error: {result.get('error')}") - if "message" in result: - print(result.get('message')) - sys.exit(1) - - curator = result.get("obj", {}) - print(f"Curator: {curator.get('name', 'Unknown')}") - print(f" Chartmetric ID: {curator.get('id')}") - print(f" User ID: {curator.get('user_id')}") - if curator.get('image_url'): - print(f" Image: {curator.get('image_url')}") - if curator.get('num_playlists'): - print(f" Playlists: {curator.get('num_playlists')}") - if curator.get('followers'): - print(f" Followers: {curator.get('followers'):,}") - - -if __name__ == "__main__": - main() diff --git a/skills/chart-metric/scripts/get_playlist.py b/skills/chart-metric/scripts/get_playlist.py deleted file mode 100644 index bd0c2b9..0000000 --- a/skills/chart-metric/scripts/get_playlist.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python3 -""" -Get playlist metadata. - -Usage: - python get_playlist.py - python get_playlist.py spotify 37i9dQZF1DXcBWIGoYBM5M - python get_playlist.py apple pl.f4d106fed2bd41149aaacabb233eb5eb - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import json -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def get_playlist(platform: str, playlist_id: str) -> dict: - """Fetch playlist metadata from Chartmetric.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/playlist/{platform}/{playlist_id}", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 404: - return {"error": "Playlist not found", "platform": platform, "playlist_id": playlist_id} - - response.raise_for_status() - return response.json() - - -if __name__ == "__main__": - if len(sys.argv) < 3: - print("Usage: python get_playlist.py ") - print("Platforms: spotify, apple, deezer, amazon") - sys.exit(1) - - result = get_playlist(sys.argv[1], sys.argv[2]) - print(json.dumps(result, indent=2)) - if "error" in result: - sys.exit(1) diff --git a/skills/chart-metric/scripts/get_similar_artists.py b/skills/chart-metric/scripts/get_similar_artists.py deleted file mode 100644 index b6d617e..0000000 --- a/skills/chart-metric/scripts/get_similar_artists.py +++ /dev/null @@ -1,195 +0,0 @@ -#!/usr/bin/env python3 -""" -Get related/similar artists based on streaming data. - -Two modes: -1. Basic (default): Uses /relatedartists endpoint -2. Advanced (--by-config): Uses /similar-artists/by-configurations with filters - -Usage: - # Basic related artists - python get_similar_artists.py [--limit 10] - python get_similar_artists.py 3380 - python get_similar_artists.py 3380 --limit 25 - - # Advanced with configuration filters - python get_similar_artists.py 2762 --by-config --audience high --genre high - python get_similar_artists.py 2762 --by-config --mood medium --musicality high - -Configuration options (at least one required when using --by-config): - --audience: high, medium, low - Similarity of Audience - --mood: high, medium, low - Similarity of Mood - --genre: high, medium, low - Similarity of Genre - --musicality: high, medium, low - Similarity of Musicality - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import argparse -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def get_related_artists(cm_id: str, limit: int = 10) -> dict: - """Fetch related artists from Chartmetric using basic endpoint.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/artist/{cm_id}/relatedartists", - headers={"Authorization": f"Bearer {token}"}, - params={"limit": limit}, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 401: - return {"error": "Unauthorized", "message": "This endpoint may require authentication."} - - if response.status_code == 404: - return {"error": "Artist not found", "chartmetric_id": cm_id} - - response.raise_for_status() - return response.json() - - -def get_similar_artists_by_config( - cm_id: str, - limit: int = 10, - offset: int = 0, - audience: str = None, - mood: str = None, - genre: str = None, - musicality: str = None -) -> dict: - """ - Fetch similar artists using advanced configuration filters. - - At least one of audience, mood, genre, or musicality must be specified. - Valid values: 'high', 'medium', 'low' - """ - token = get_token() - - params = {"limit": limit, "offset": offset} - if audience: - params["audience"] = audience - if mood: - params["mood"] = mood - if genre: - params["genre"] = genre - if musicality: - params["musicality"] = musicality - - # Validate at least one config is set - if not any([audience, mood, genre, musicality]): - return {"error": "Invalid parameters", "message": "At least one of audience, mood, genre, or musicality is required."} - - response = requests.get( - f"{API_BASE}/artist/{cm_id}/similar-artists/by-configurations", - headers={"Authorization": f"Bearer {token}"}, - params=params, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 401: - return {"error": "Unauthorized", "message": "This endpoint may require authentication."} - - if response.status_code == 404: - return {"error": "Artist not found", "chartmetric_id": cm_id} - - response.raise_for_status() - return response.json() - - -def main(): - parser = argparse.ArgumentParser(description="Get related/similar artists") - parser.add_argument("chartmetric_id", help="Chartmetric artist ID") - parser.add_argument("--limit", "-l", type=int, default=10, - help="Number of related artists (default: 10)") - parser.add_argument("--offset", type=int, default=0, - help="Offset for pagination (default: 0)") - - # Advanced configuration mode - parser.add_argument("--by-config", action="store_true", - help="Use advanced similar-artists/by-configurations endpoint") - parser.add_argument("--audience", choices=["high", "medium", "low"], - help="Similarity of audience (requires --by-config)") - parser.add_argument("--mood", choices=["high", "medium", "low"], - help="Similarity of mood (requires --by-config)") - parser.add_argument("--genre", choices=["high", "medium", "low"], - help="Similarity of genre (requires --by-config)") - parser.add_argument("--musicality", choices=["high", "medium", "low"], - help="Similarity of musicality (requires --by-config)") - - args = parser.parse_args() - - # Use advanced endpoint if --by-config or any config option is set - use_config = args.by_config or any([args.audience, args.mood, args.genre, args.musicality]) - - if use_config: - result = get_similar_artists_by_config( - args.chartmetric_id, - limit=args.limit, - offset=args.offset, - audience=args.audience, - mood=args.mood, - genre=args.genre, - musicality=args.musicality - ) - else: - result = get_related_artists(args.chartmetric_id, args.limit) - - if "error" in result: - print(f"Error: {result.get('error')}") - if "message" in result: - print(result.get('message')) - sys.exit(1) - - # Handle different response structures - if use_config: - obj = result.get("obj", {}) - artists = obj.get("data", []) - total = obj.get("total", 0) - print(f"Similar Artists by Configuration ({len(artists)} shown, {total:,} total):\n") - else: - artists = result.get("obj", []) - print(f"Related Artists ({len(artists)} found):\n") - - for artist in artists: - print(f"- {artist.get('name')}") - print(f" Chartmetric ID: {artist.get('id')}") - - # Show similarity score if available (from by-config endpoint) - if artist.get('similarity') is not None: - print(f" Similarity: {artist.get('similarity'):.2f}") - - # Show career stage if available - if artist.get('career_stage'): - print(f" Career Stage: {artist.get('career_stage')}") - - # Show rank - rank = artist.get('rank') or artist.get('cm_artist_rank') - if rank: - print(f" CM Rank: {rank:,}") - - # Show Spotify data - sp_followers = artist.get('sp_followers') or artist.get('spotify_followers') - if sp_followers: - print(f" Spotify Followers: {sp_followers:,}") - - if artist.get('sp_monthly_listeners'): - print(f" Monthly Listeners: {artist.get('sp_monthly_listeners'):,}") - - print() - - -if __name__ == "__main__": - main() diff --git a/skills/chart-metric/scripts/get_token.py b/skills/chart-metric/scripts/get_token.py deleted file mode 100644 index 8790b79..0000000 --- a/skills/chart-metric/scripts/get_token.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python3 -""" -Get or refresh Chartmetric access token. -Token is cached in /tmp/chartmetric_token.json - -Usage: - python get_token.py - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import os -import json -import time -import requests -from pathlib import Path - -CACHE_FILE = Path("/tmp/chartmetric_token.json") -API_BASE = "https://api.chartmetric.com/api" - - -def get_token() -> str: - """Get a valid access token, refreshing if needed.""" - - # Check cache - if CACHE_FILE.exists(): - try: - cached = json.loads(CACHE_FILE.read_text()) - if cached["expires_at"] > time.time() + 60: - return cached["access_token"] - except (json.JSONDecodeError, KeyError): - pass # Cache invalid, refresh - - # Refresh token - refresh_token = os.environ.get("CHARTMETRIC_REFRESH_TOKEN") - if not refresh_token: - raise ValueError("CHARTMETRIC_REFRESH_TOKEN environment variable not set") - - response = requests.post( - f"{API_BASE}/token", - json={"refreshtoken": refresh_token} - ) - response.raise_for_status() - data = response.json() - - # Cache it - cached = { - "access_token": data["token"], - "expires_at": time.time() + data.get("expires_in", 3600) - } - CACHE_FILE.write_text(json.dumps(cached)) - - return cached["access_token"] - - -if __name__ == "__main__": - token = get_token() - print(token) diff --git a/skills/chart-metric/scripts/get_track.py b/skills/chart-metric/scripts/get_track.py deleted file mode 100644 index 9aee8d8..0000000 --- a/skills/chart-metric/scripts/get_track.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 -""" -Get track metadata by Chartmetric ID. - -Usage: - python get_track.py - python get_track.py 128613854 - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import json -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def get_track(cm_id: str) -> dict: - """Fetch track metadata from Chartmetric.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/track/{cm_id}", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 404: - return {"error": "Track not found", "chartmetric_id": cm_id} - - response.raise_for_status() - return response.json() - - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python get_track.py ") - sys.exit(1) - - result = get_track(sys.argv[1]) - print(json.dumps(result, indent=2)) - if "error" in result: - sys.exit(1) diff --git a/skills/chart-metric/scripts/get_track_by_spotify.py b/skills/chart-metric/scripts/get_track_by_spotify.py deleted file mode 100644 index cd81dcc..0000000 --- a/skills/chart-metric/scripts/get_track_by_spotify.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -""" -Lookup track by Spotify ID or URL. - -Usage: - python get_track_by_spotify.py - python get_track_by_spotify.py 0VjIjW4GlUZAMYd2vXMi3b - python get_track_by_spotify.py "https://open.spotify.com/track/0VjIjW4GlUZAMYd2vXMi3b" - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import json -import re -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def extract_spotify_id(url_or_id: str) -> str: - """Extract Spotify track ID from URL or return as-is if already an ID.""" - match = re.search(r"track[/:]([a-zA-Z0-9]+)", url_or_id) - if match: - return match.group(1) - return url_or_id - - -def get_track_by_spotify(spotify_id: str) -> dict: - """Lookup Chartmetric track by Spotify ID.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/track/spotify/{spotify_id}/get-ids", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - if response.status_code == 402: - return {"error": "Payment Required", "message": "Check your Chartmetric subscription."} - - if response.status_code == 404: - return {"error": "Track not found in Chartmetric", "spotify_id": spotify_id} - - response.raise_for_status() - return response.json() - - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python get_track_by_spotify.py ") - sys.exit(1) - - spotify_id = extract_spotify_id(sys.argv[1]) - result = get_track_by_spotify(spotify_id) - print(json.dumps(result, indent=2)) - if "error" in result: - sys.exit(1) diff --git a/skills/chart-metric/scripts/list_festivals.py b/skills/chart-metric/scripts/list_festivals.py deleted file mode 100644 index ce7154b..0000000 --- a/skills/chart-metric/scripts/list_festivals.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python3 -""" -List music festivals. - -Usage: - python list_festivals.py - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import json -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def list_festivals() -> dict: - """Fetch festival list from Chartmetric.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/festival/list", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - response.raise_for_status() - return response.json() - - -if __name__ == "__main__": - result = list_festivals() - festivals = result.get("obj", []) - - print(f"Found {len(festivals)} festivals:\n") - for fest in festivals[:30]: - print(f"- {fest.get('name')}") - if fest.get('city'): - print(f" Location: {fest.get('city')}, {fest.get('country', '')}") - print() diff --git a/skills/chart-metric/scripts/list_genres.py b/skills/chart-metric/scripts/list_genres.py deleted file mode 100644 index d56790d..0000000 --- a/skills/chart-metric/scripts/list_genres.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python3 -""" -List all Chartmetric genres. - -Usage: - python list_genres.py - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import json -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def list_genres() -> dict: - """Fetch all genres from Chartmetric.""" - token = get_token() - - response = requests.get( - f"{API_BASE}/genres", - headers={"Authorization": f"Bearer {token}"}, - timeout=10 - ) - - response.raise_for_status() - return response.json() - - -if __name__ == "__main__": - result = list_genres() - genres = result.get("obj", []) - - print(f"Found {len(genres)} genres:\n") - for genre in genres[:50]: - print(f"- {genre.get('name')} (ID: {genre.get('id')})") - - if len(genres) > 50: - print(f"\n... and {len(genres) - 50} more") diff --git a/skills/chart-metric/scripts/search_artist.py b/skills/chart-metric/scripts/search_artist.py deleted file mode 100644 index f39f222..0000000 --- a/skills/chart-metric/scripts/search_artist.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 -""" -Search for an artist by name in Chartmetric. - -Usage: - python search_artist.py "Artist Name" - python search_artist.py "Drake" --limit 10 - -Environment: - CHARTMETRIC_REFRESH_TOKEN - Your Chartmetric refresh token -""" - -import sys -import json -import argparse -import requests -from get_token import get_token - -API_BASE = "https://api.chartmetric.com/api" - - -def search_artist(name: str, limit: int = 5) -> dict: - """Search for artists by name. - - Uses the /search endpoint with type=artists. - """ - token = get_token() - - response = requests.get( - f"{API_BASE}/search", - headers={"Authorization": f"Bearer {token}"}, - params={"q": name, "type": "artists", "limit": limit}, - timeout=10 - ) - - if response.status_code == 402: - return { - "error": "Payment Required", - "message": "Your Chartmetric API subscription may be expired or this endpoint requires a higher tier. Check your account at chartmetric.com or contact hi@chartmetric.com" - } - - response.raise_for_status() - return response.json() - - -def main(): - parser = argparse.ArgumentParser(description="Search for an artist by name") - parser.add_argument("name", help="Artist name to search for") - parser.add_argument("--limit", "-l", type=int, default=5, help="Number of results") - parser.add_argument("--json", action="store_true", help="Output raw JSON") - - args = parser.parse_args() - result = search_artist(args.name, args.limit) - - if args.json: - print(json.dumps(result, indent=2)) - return - - # Check for error responses - if "error" in result: - print(f"Error: {result.get('error')}") - if "message" in result: - print(result.get('message')) - return - - artists = result.get("obj", {}).get("artists", []) - - if not artists: - print(f"No artists found for '{args.name}'") - return - - print(f"Found {len(artists)} artists:\n") - for artist in artists: - print(f"- {artist.get('name')}") - print(f" Chartmetric ID: {artist.get('id')}") - if artist.get('spotify_id'): - print(f" Spotify ID: {artist.get('spotify_id')}") - print() - - -if __name__ == "__main__": - main() diff --git a/skills/content-creation/SKILL.md b/skills/content-creation/SKILL.md deleted file mode 100644 index 38728cf..0000000 --- a/skills/content-creation/SKILL.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -name: content-creation -description: Create content for artists and record labels — short-form videos, images, captions, lipsync clips, and more. Use when the user asks to create content, make a video, generate an image, produce a TikTok or Reel, create a promotional clip, add captions, or create visual content for an artist. Also use when iterating on content quality — regenerating images, trying different audio, adjusting text, or upscaling for higher quality. ---- - -# Content Creation - -Create visual content for artists and record labels using the Recoup API. You can access every capability through either the CLI (`recoup content`) or the REST API (`/api/content/*`). - -## What You Can Do - -| Capability | CLI | API | -|------------|-----|-----| -| Generate image | `recoup content image` | `POST /api/content/image` | -| Generate video | `recoup content video` | `POST /api/content/video` | -| Generate caption | `recoup content caption` | `POST /api/content/caption` | -| Transcribe audio | `recoup content transcribe` | `POST /api/content/transcribe` | -| Edit (trim, crop, overlay, mux audio) | `recoup content edit` | `PATCH /api/content` | -| Upscale image or video | `recoup content upscale` | `POST /api/content/upscale` | -| Analyze video | `recoup content analyze` | `POST /api/content/analyze` | -| Full pipeline (one shot) | `recoup content create` | `POST /api/content/create` | - -Image and video generation are async — they return a `runId`. Poll with `recoup tasks status --run {runId} --json` (or `GET /api/tasks/runs?runId={runId}`) until `status` is `COMPLETED`, then read `output`. - -Full API docs: https://developers.recoupable.com/content-agent - -## Video Modes - -`generate-video` supports 6 modes. Set `--mode` (CLI) or `mode` (API) explicitly, or omit it and the mode is inferred from inputs. - -| Mode | What it does | Required inputs | -|------|-------------|-----------------| -| `prompt` | Create from text description | prompt | -| `animate` | Animate a still image | image + prompt | -| `reference` | Use image as style reference (not first frame) | image + prompt | -| `extend` | Continue an existing video (max 8s input) | video + prompt | -| `first-last` | Transition between two images | image + end image + prompt | -| `lipsync` | Sync face movement to audio | image + audio | - -### When to use which mode - -- "I need a cinematic establishing shot" → `prompt` -- "I have a photo of the artist, make it come alive" → `animate` -- "I have a photo of the artist, put them in a new scene" → `reference` -- "This clip is great but too short" → `extend` -- "I want a smooth transition from the studio to the stage" → `first-last` -- "I need the face to sing along to this audio" → `lipsync` - -## Templates - -Templates are optional shortcuts — curated creative recipes that pre-fill prompts, reference images, style rules, and edit operations. Every capability works without a template. - -List available templates: - -```bash -recoup content templates --json -``` - -Or via API: `GET /api/content/templates` - -### Override priority - -When using a template, your explicit parameters always win: - -1. **Your parameters** — highest priority -2. **Artist context** — if the artist has a style guide, it personalizes the template -3. **Template defaults** — lowest priority - -## Workflow - -### Step by step (full creative control) - -```bash -# 1. Transcribe audio -recoup content transcribe --url {audioUrl} --json - -# 2. Generate image -recoup content image --prompt "{scene description}" \ - --reference-image {referenceImageUrl} --json - -# 3. (Optional) Upscale image -recoup content upscale --url {imageUrl} --type image --json - -# 4. Generate video -recoup content video --mode animate --image {imageUrl} \ - --prompt "{how to animate}" --json - -# 5. Generate caption -recoup content caption --topic "{topic}" --length short --json - -# 6. Edit final video -recoup content edit --url {videoUrl} --audio {audioUrl} \ - --overlay-text "{caption}" --json - -# 7. (Optional) Analyze the result -recoup content analyze --video {videoUrl} \ - --prompt "Rate this 1-10 for social media engagement" --json -``` - -### One-shot pipeline - -When you just want a finished video without controlling each step: - -```bash -recoup content create --artist {artistAccountId} --json -recoup content create --artist {artistAccountId} --template artist-caption-bedroom --lipsync --json -``` - -Or via API: `POST /api/content/create` with `{ "artist_account_id": "..." }` - -### Lipsync - -```bash -recoup content video --mode lipsync \ - --image {faceUrl} --audio {audioUrl} --json - -recoup content edit --url {videoUrl} \ - --crop-aspect 9:16 --overlay-text "{caption}" --json -``` - -## Edit Operations - -The edit command applies operations in a single processing pass: - -| Operation | CLI flags | What it does | -|-----------|-----------|-------------| -| Trim | `--trim-start {s} --trim-duration {s}` | Cut a time window | -| Crop | `--crop-aspect {ratio}` | Crop to aspect ratio (e.g. 9:16) | -| Overlay text | `--overlay-text {text} --text-color {color} --text-position {pos}` | Add on-screen text | -| Mux audio | `--mux-audio {url}` | Add audio track to video | -| Template | `--template {name}` | Use template's preset operations | - -## Evaluating Output - -You can't see pixels — but `analyze` can. Use it throughout the workflow to evaluate output. - -**What to look for:** -- **QA** — artifacts, glitches, subject consistency, motion naturalness -- **Taste** — does the opening hook attention? Does visual energy match audio? -- **Platform readiness** — text readability, vertical crop framing, audio-visual sync - -Analyze after each creative checkpoint, not just at the end. - -## Iteration - -Each step is independent. Redo any step without rerunning the whole pipeline: - -- Bad image? Regenerate with a different prompt or reference -- Caption too long? Regenerate with `--length short` -- Video glitchy? Analyze it, then regenerate with adjusted params -- Clip too short? Use `extend` mode to continue it -- Low quality? Upscale the image or video -- Everything good but wrong caption? Just re-run the edit step diff --git a/skills/issue-implementation/SKILL.md b/skills/issue-implementation/SKILL.md deleted file mode 100644 index 2e67840..0000000 --- a/skills/issue-implementation/SKILL.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -name: issue-implementation -description: Deliver a tracked GitHub issue end-to-end in the Recoupable house style — documentation-driven (OpenAPI/contract first), then test-driven (red→green) implementation that matches the docs, verified against the live preview deployment, with results posted to the PR. Use when the user says "implement this issue", "build out issue #N", "ship the issue", "do the implementation", "take this issue and build it", or hands you a tracking issue (usually from the issue-management skill) to deliver. Covers docs-first ordering, the TDD loop, preview verification, the docs↔API↔reality reconciliation, and PR review hygiene. Pairs with the issue-management skill (which writes the issue this one implements). ---- - -# Implementing a Tracked Issue - -How we take a tracking issue from open to shipped at Recoupable. The bar: **the docs, the API, and the live preview all tell the same story — and every claim you make on the PR is something you actually ran.** No contract drift, no "should work," no untested assertions. - -This is the delivery counterpart to the **issue-management** skill: that one writes the issue; this one implements it. The gold-standard reference slice this skill is built from is [chat#1789](https://github.com/recoupable/chat/issues/1789) (the tracking issue) → [docs#236](https://github.com/recoupable/docs/pull/236) (contract) → [api#653](https://github.com/recoupable/api/pull/653) (implementation). Read those three together to see the whole loop. - -## Prerequisite: implement against a real contract, not a vibe - -Only start when the issue is a proper spec — it should carry a **Goal**, a **proposed contract** (endpoint, params, response shape), a **merge sequencing** block (docs → api), **Done-when** acceptance criteria, and **source references**. If it doesn't, stop and write/upgrade it with the **issue-management** skill first. Implementing against a vague issue is how you ship the wrong thing. - -## The loop - -``` -1. Read the issue + the ground (contract, Done-when, conventions, the sibling to mirror) -2. Docs first — the contract (OpenAPI/reference PR; merge target: main) -3. API by TDD, matching the docs (red→green→refactor; PR target: test; links the docs PR) -4. Wait for the preview deployment (poll the PR-head commit until Ready) -5. Test the PR against the preview (every Done-when criterion, against real data) -6. Reconcile docs ↔ API ↔ live results (live response is ground truth; fix drift) -7. Comment on the PR as you test (results table; triage bot findings) -8. Hand off in merge order (docs → api; update the issue to Done on merge) -``` - -Run it in order. Docs-first and tests-first are not optional — they are the method. - -## 1. Read the issue + the ground - -- **Extract from the issue:** the exact contract (path, params, response envelope, status codes), the merge order, every Done-when checkbox (these are your test plan), and the source references (verify any external API doc the issue cites is still accurate). If a cited doc is **client-side-rendered** (`WebFetch` returns an empty JS shell), read it with a **browser MCP** — e.g. Chrome DevTools `navigate_page` then `evaluate_script` to pull the rendered params/response — not `WebFetch`. -- **Read the ground:** each target submodule's `CLAUDE.md`/`AGENTS.md` (branch rules, test/lint commands, response/validation/auth conventions). -- **Find the closest sibling and mirror it — don't invent.** Almost every endpoint has a neighbor that already solves 90% of the shape (auth, validation, response wrapping, error envelope, credits). Build by analogy to it; consistency with the immediate neighbor beats cleverness. - -## 2. Docs first — the contract - -Documentation-driven development: the docs/OpenAPI change is the contract, and it merges before the code that fulfills it. - -- Branch from `main` in `docs`. Mirror the nearest sibling endpoint's OpenAPI block — params, **reuse existing schemas** (DRY), the shared error-response schema, the reference-page frontmatter, and the nav entry (adding the page to `docs.json` is what surfaces it in the generated `llms.txt`). -- **Byte-safe edits to large/generated JSON.** Before bulk-inserting into a big OpenAPI file, round-trip it (`json.dumps(json.load(f), indent=2, ensure_ascii=False)` and diff against the original) to confirm your serializer reproduces it exactly — then load → add keys → dump. This keeps the diff additive instead of a whole-file reformat. Always re-validate the JSON parses. -- **Accuracy over symmetry.** Document only what the API will actually return. Do not add a response code or field just because a sibling has it — an undocumented-but-real gap is better than a documented-but-false one. -- Commit, push, open the docs PR (base `main`). This is step 1 of the merge order. - -## 3. API by TDD, matching the docs - -Branch per the repo's rule (Recoupable `api` PRs target `test`). Mirror the sibling implementation's layering (route → handler → validate → data function → response shaping; auth; credits). - -**Red → green → refactor, one unit at a time:** - -1. Write the failing test **first** in `__tests__/` (mock dependencies like the sibling tests do). -2. Run it and **confirm it fails** (RED) — usually "module not found" for a new file, or a real assertion failure for new behavior. Never write code and test in the same step. -3. Write the **minimum** implementation to pass (GREEN). -4. Refactor — **one exported function per file** (SRP). Extract inline helpers into their own lib files when a reviewer would (see how `gateChatStreamStart` / `waitForTerminalRunStatus` were pulled out). - -Then: the implementation must match the **documented** contract exactly (params, response envelope, status codes). Run the **full domain test suite** (not just your files) to prove no regressions, then `tsc --noEmit` and lint. Commit, push, open the api PR (base `test`) — link the issue and the docs PR, and state the docs→api merge order in the body. - -## 4. Wait for the preview deployment - -- Find the preview for **your pushed commit** — `gh api repos///deployments?sha=` → its `/statuses` → `environment_url`, or the Vercel CLI (`vercel ls --scope ` / `vercel inspect`). -- **Confirm it's built from your commit**, not a stale earlier preview — verify the deployment's sha. Testing a stale preview is a classic false-positive/false-negative trap. -- Poll until `Ready`. Background the poll on long builds; don't block. - -## 5. Test the PR against the preview - -Turn every **Done-when** criterion into a live check against the real preview URL: - -- **Happy path** — the documented success response, with a real fixture (real id/ISRC/etc.). -- **Every status code** — including a **deliberately bad input** to confirm each 4xx (a non-UUID, an unknown id, a missing required param). This is how you confirm the documented error codes are real. -- **Auth** — 401 without a key; confirm no secret/env value is echoed in any response. -- **Cross-check the source of truth** — when it sharpens the assertion, query the DB / upstream directly (e.g. confirm a row's state, or that a per-item number is materially smaller than an aggregate). Capture **hard numbers**, not "looks right." - -## 6. Reconcile docs ↔ API ↔ reality - -The **live response is ground truth.** Compare it field-by-field and code-by-code against the documented contract: - -- If the live response carries fields or status codes the docs **missed**, add them to the docs. (In the reference slice, the live response carried a top-level `source_ids` and a `track_info.songstats_track_id` the spec lacked — the docs were patched to match.) -- If the docs claim something the API **doesn't do**, fix whichever is wrong — usually the docs, sometimes the code (e.g. the contract said "exactly one identifier" but the validator allowed several → tighten the validator). - -All three must agree before you call it done. This step is the entire point of the loop — it's what prevents the doc-drift the stop endpoint had. - -## 7. Comment on the PR as you test - -- Post your verification as a **results table on the PR** — *documented* vs *actual* for each path, with the hard numbers and status codes you observed. Do this on the api PR; comment on the docs PR too when you reconciled it. -- **Reply on review threads** when you address them, citing the commit. -- **Triage bot review findings critically — validate before applying.** A bot's "P1" can be a false positive (a suggested revert that would reintroduce a bug; a "missing 501" the endpoint never emits). Confirm against the code/live behavior, then either fix it or reply with the reasoning for not. Don't rubber-stamp, don't blanket-dismiss. - -## 8. Hand off in merge order - -- Merge order is **docs → api** (the contract lands first). Honor hard dependencies (a database migration before the api that reads it). -- **Never merge without explicit user approval.** When the user approves: merge, then promote per the repo flow (Recoupable: squash → `test`, then `test` → `main` via a release PR, then sync `test` with `main`), checking the release scope first. -- On merge, **update the tracking issue to Done** with the **issue-management** skill — a closure note (PR links, ✅ ISO date, merge path, what shipped, and a Verified clause citing the live results), and check off the Done-when boxes you actually verified. - -## Principles - -- **Docs-first, always** — the contract leads; code fulfills it. -- **Tests before code** — RED before GREEN, every unit; one exported function per file. -- **Verify against reality** — every claim on a PR is something you ran; capture hard numbers and ISO dates. -- **Accuracy over symmetry** — never document a response the API doesn't emit; match the immediate sibling's conventions over global ideals. -- **Minimal, additive diffs** on generated/large files; re-validate they still parse. -- **Never echo secrets** — reference the env-var name (`SONGSTATS_API_KEY`), never a value. - -## Quick reference - -```bash -# Find + confirm the preview for the commit you pushed -gh api repos/recoupable/api/deployments?sha= --jq '.[].id' -gh api repos/recoupable/api/deployments//statuses --jq '[.[]|select(.state=="success")|.environment_url][0]' -vercel ls api --scope recoup # or: vercel inspect --scope recoup - -# Run a single test RED→GREEN, then the whole domain + types + lint -pnpm exec vitest run lib//__tests__/.test.ts -pnpm exec vitest run lib/ && pnpm exec tsc --noEmit && pnpm exec eslint - -# Test the preview, then post results on the PR -curl -s -w "\nHTTP %{http_code}\n" "/api/..." -H "x-api-key: " -gh pr comment --repo recoupable/api --body-file results.md -``` - -## Checklist before you call it done - -- [ ] Implemented against a real tracking issue (contract + Done-when present); upgraded it first if not. -- [ ] Docs PR opened **first**; api PR links it and states the docs→api merge order. -- [ ] Every api unit was **RED before GREEN**; full domain suite + `tsc` + lint all clean. -- [ ] Preview confirmed **built from your commit**; **every Done-when criterion** exercised against it with real data. -- [ ] Docs ↔ API ↔ live results **agree** (reconciled and re-pushed if not). -- [ ] Verification **posted on the PR**; bot findings **triaged** (validated, not rubber-stamped). -- [ ] No secret value echoed anywhere — env-var names only. -- [ ] On merge: promoted per the repo flow and the tracking issue moved to **Done** (issue-management). diff --git a/skills/issue-management/SKILL.md b/skills/issue-management/SKILL.md deleted file mode 100644 index 6eb5573..0000000 --- a/skills/issue-management/SKILL.md +++ /dev/null @@ -1,129 +0,0 @@ ---- -name: issue-management -description: Write and maintain high-signal GitHub issues — especially long-lived tracking issues that coordinate multi-PR, multi-repo efforts — in the Recoupable house style. Use when the user says "write an issue", "create a tracking issue", "open an issue for this work", "update the issue", "check off / close out an item", "log what shipped on the issue", "maintain the issue", or when capturing a plan, cutover, migration, or cleanup that spans more than one PR. Covers structure, the Open→Done lifecycle, evidence/linking rules, and acceptance criteria. ---- - -# Writing & Maintaining Issues - -How we write GitHub issues at Recoupable. The bar: **a teammate (or a cold agent) can read the issue and know exactly what's done, what's left, why each decision was made, and how to verify any claim — without re-deriving it from git history.** Issues are persistent memory, not a TODO dump. - -Most substantial work gets a **tracking issue**: a long-lived issue that coordinates a multi-PR (often multi-submodule) effort. The two reference issues this skill is built from are [chat#1747](https://github.com/recoupable/chat/issues/1747) (cutover plan) and [chat#1767](https://github.com/recoupable/chat/issues/1767) (post-cutover cleanup). Read them if you want the gold standard. - -## Issues live in a repository - -Every issue belongs to one repo (`gh issue create --repo recoupable/`). There is no org-level issue — cross-repo work lives in one "home" repo's tracking issue that *links out* to PRs/issues in the others. - -**File every issue in `recoupable/chat`** — even when the actual code lands in `api`, `database`, or `docs`. One home repo means devs have a single place to find open work. Do **not** pick the repo by "where the diff lands"; link out to sibling-repo PRs/files by full ref instead (e.g. the chat cutover tracker lives in `chat` even though most commits were in `api`). If an issue gets opened in the wrong repo, move it: `gh issue transfer recoupable/chat --repo ` (preserves body/comments, leaves a redirect). - -## Anatomy of a tracking issue - -Use these sections in this order. Drop sections that don't apply; never reorder the ones you keep. - -1. **Lead paragraph (no heading).** One or two sentences: what this issue tracks, the current prod/live status, and a link to the parent or sibling issue. Example: *"Tracking issue for cleanup that follows the X→Y cutover ([chat#1747](url), shipped to prod 2026-06-01). The cutover is fully live; this issue captures the remaining items."* -2. **## Goal.** The end state in concrete terms — what "done" means at the macro level, and the downstream consequences. Name the files, endpoints, and tables involved. -3. **## What shipped (context)** *(optional).* Hard numbers that ground the reader: commit/PR counts, rows migrated, latency measured. *"api/main has 23 commits / 13 PRs … 17,991 sessions with correct artist_id; 0 stragglers remaining."* -4. **## Done.** Completed items as a checked list — see the Done-item format below. -5. **## Open — .** Remaining work as unchecked items. Bucket by theme or priority (`Open — cleanup + follow-ups`, `Open — next up (in merge order)`, `Open — pre-existing docs gaps (low priority)`). See the Open-item format below. -6. **## Open — Phase N (LAST, only after …).** Sequenced/gated work. State the gate explicitly in the heading and the body — *"only after the cutover bundle is stable on `main` for some time."* -7. **## Architecture decisions.** Durable decisions with rationale, so they don't get re-litigated. Bold the decision, then explain. *"**chatId source of truth is api.** createSessionHandler mints chat.id; the client uses it as …"* -8. **## Accepted regressions / tradeoffs** *(optional).* What you knowingly gave up and may re-add later. Makes the cost explicit instead of silent. -9. **## Inherited gaps / Source references** *(optional).* Cross-links to other trackers, the originating PRs, key files (with paths), and prior art. - -## The Open → Done lifecycle (the most important rule) - -Items are **not** closed by flipping `- [ ]` to `- [x]`. When an item ships, **rewrite it into a closure note** and move it under `## Done`: - -``` -- [x] **chat[#1765](url) — drop legacy useChatTransport branch + delete app/chat/[roomId]/page.tsx.** - ✅ Shipped 2026-06-03 (merged to `test`, then `test`→`main`; both synced). - . - Verified end-to-end on preview: new chat → session-scoped URL, transport POSTs - /api/chat/workflow with sessionId+chatId+bearer, assistant streams, legacy route 404s. -``` - -Every Done item carries: **bold lead-in + PR link**, **✅ + ISO ship date**, **merge path** (which branches, synced?), **what changed**, and a **Verified** clause stating *how* you confirmed it and *on what* (preview/prod, what you clicked, what the response showed). If a hypothesis turned out wrong, say so and link the evidence — see how #1767 documents the "3 sessions per click" item turning out to already be fixed, with the `isLoading`→`isPending` root cause and a link to the source line range. - -## Open-item format - -An open item is a mini-spec, not a one-liner. For anything non-trivial, include: - -``` -- [ ] **.** () - - **Why:** - - **Fix:** - - **Done when:** -``` - -Real example (the model-selector bug from #1767): *Why* — the workflow reads `chats.model_id` but the UI only puts `model` in the request body. *Fix* — on model change, `PATCH .../chats/{chatId}` with `{ modelId }`. *Done when* — pick a non-default model → send → `usage_events.model_id` shows the model you selected, not the default. A reader can pick this up cold and finish it. - -Note explicit **blocked-by** relationships inline (*"No blockers — unblocked by #1760"*). If items have a merge order, say "in merge order; pick from the top." - -### Merge sequencing (documentation-driven development) - -We follow **documentation-driven development**: the docs/contract is written and reviewed first, then implemented against. When a unit of work spans repos, sequence the PRs **docs → database → api/app** — the `docs` OpenAPI/spec change is the contract the rest fulfills, so it leads even though it's usually independent. Within that, still honor hard dependencies (e.g. a `database` migration must land before the `api` code that reads the new table). State the order explicitly in a "Merge sequencing" block and say *why* each step precedes the next (contract-first vs. hard dependency). - -## Linking & evidence rules - -- **Link every claim.** PRs as `[chat#1765](url)`, commits by SHA, files by path, source by line range (`[source: useArtistsRoster.ts L60-86](url)`). A claim without a link is a claim someone has to re-verify. -- **Use hard numbers**, not adjectives. "10.9s cold", "46,212 memories → chat_messages", "13 chats returned, archived row gone" beats "slow", "lots", "works". -- **ISO 8601 dates** (`2026-06-03`), always. -- **Cross-repo links** by full ref (`recoupable/api#605`) so they resolve from any repo. -- **Record rationale when you close or supersede a PR**: *"closed #1754 (superseded; chat-side direct-DB approach was wrong layer)."* -- **Never paste secrets into an issue.** No API keys, tokens, or credentials — issues are often public and always long-lived/indexed. Reference the env var or store instead (*"`SONGSTATS_API_KEY`, set in Vercel"*), not the value. Same for any key handed over in a thread. - -## Maintaining an issue over time - -- When you finish a unit of work, **update the issue in the same session** — move the item to Done with its closure note. Don't let the issue drift from reality. -- Keep the lead paragraph's status line current ("fully live on prod"). -- **Keep the title current too.** The title is maintained state, not a fixed label — when scope changes, edit it. (#1777's title carried `+ bring-your-own-key` after that path was dropped; it had to be re-edited to match.) -- Comment for time-stamped updates or test results; edit the body for the canonical current state. (Manual test results generally go in PR comments, not the issue body.) -- Close the issue only when **every** item is in Done or has been explicitly moved to a follow-up tracker (link it). - -### Decision changes & reversals (supersede, don't erase) - -When the plan reverses (e.g. #1777 pivoted from "keep Chartmetric behind a BYO-key router" to "delete all Chartmetric code, SongStats-only" on a YAGNI call), record the change so the *old* reasoning stays auditable — don't silently rewrite history: - -- **Add a dated decision callout near the top** (a `>` blockquote under the lead): what changed, **who decided + when**, and the one-line rationale. Attribution + ISO date matter most for decisions that came from a discussion, not from code. -- **Supersede, don't delete.** Strike through (`~~…~~`) or annotate the now-dead item with *"Dropped — …"* / *"Supersedes the earlier X plan."* rather than removing it. A reader (or a cold agent) should see the prior plan and why it was abandoned, so it isn't re-proposed. -- **Update every downstream section**: Goal, the affected Open items, Architecture decisions (mark the reversed one *"supersedes the earlier …"*), the title, and Source references (link the decision thread). -- **Re-anchor in-flight PRs to the new plan.** If an open PR was built for the old approach, say what now has to change before it can merge (e.g. #1777 flags that api#635's Chartmetric branch must be stripped pre-merge, and that verification must re-run after). - -## Smaller / non-tracking issues - -Not every issue is a tracker. A bug report or single-task issue still earns its keep with: a one-line **summary**, **steps to reproduce** (or the trigger), **expected vs actual**, and **Done when** acceptance criteria. Link the suspected code with file:line. Skip the Goal/Architecture/Phase scaffolding — that's for multi-PR efforts only. Match the weight of the issue to the weight of the work. - -## Scope to the change, not the outcome - -An issue tracks the **work to be done**, not the business goal that consumes it. Scope it to one layer/change and keep downstream logic that runs *after* the issue is solved **out of scope** — it bloats the issue and muddies what "done" means. - -- **Title names the deliverable, not the outcome** — "Add `GET /api/research/track/historic-stats`", not "annualize catalog → dashboard value". (Sharper version of keeping the title current.) -- **Business context gets exactly one perspective line** — in the lead, flagged out-of-scope: enough to know who needs this and why, never repeated through Goal / steps / Done when. -- **Every Done-when item must be verifiable within scope** by whoever implements it — not a downstream outcome (a dashboard number, a calibration to a comp, a revenue figure) the implementer can't check. - -The smell: domain terms from a *different* layer (e.g. a business metric in an API/docs issue) showing up in the Goal, the steps, and the acceptance criteria. State the cross-layer context once, then stay in your layer. - -## gh quick reference - -```bash -# Create (body from a file keeps markdown intact — preferred for long issues) -gh issue create --repo recoupable/chat --title "Post-cutover cleanup + follow-up items" --body-file issue.md - -# Edit the canonical body as work progresses -gh issue edit --repo recoupable/chat --body-file issue.md - -# Time-stamped update or test result -gh issue comment --repo recoupable/chat --body "Verified on prod 2026-06-03: …" - -# Inspect (see the two reference trackers) -gh issue view 1747 --repo recoupable/chat -gh issue view 1767 --repo recoupable/chat -``` - -## Checklist before you post / update - -The non-negotiables — structure/format is covered by the sections above; these are the things most often gotten wrong: - -- [ ] Shipped items are **closure notes under `## Done`** (PR link, ✅ ISO date, merge path, what changed, **Verified**) — never a bare `[x]`. -- [ ] Every claim is **linked** (PR/SHA/file:line); numbers are **hard**, dates **ISO**; **no secrets** (env-var name, never a value). -- [ ] **Title + lead reflect current reality** (scope + live status); reversals superseded, not erased. -- [ ] **Scoped to the change** — cross-layer/business context stated once; every Done-when verifiable in scope. diff --git a/skills/music-industry-research/SKILL.md b/skills/music-industry-research/SKILL.md deleted file mode 100644 index fbe8cae..0000000 --- a/skills/music-industry-research/SKILL.md +++ /dev/null @@ -1,113 +0,0 @@ ---- -name: music-industry-research -description: Music industry research via the Recoup `/api/research/*` REST endpoints (backed by Songstats). Use when the user asks about an artist's analytics, streaming/follower numbers, audience demographics, playlist placements, similar artists, catalog/tracks, career activity, or any music industry research. Also use for finding people in the industry (managers, A&R), extracting data from URLs, or enriching entities with structured web research. Triggers include "Spotify followers", "monthly listeners", "TikTok followers", "Instagram audience", "playlist pitching", "competitive analysis", "how is [artist] doing", "research [artist]", "find me [people]", or any question about an artist's performance, market position, or industry contacts. ---- - -# Recoup Research - -Music industry research through the Recoup API. All endpoints live under `https://api.recoupable.com/api/research` and authenticate with `x-api-key`. The research API is backed by **Songstats**, so entity IDs are short alphanumeric strings like `wjcgfd9i` (artists) or `1ik97vot` (tracks) — **not** the long numeric Chartmetric IDs older versions of this skill used. - -```bash -export RECOUP_API_KEY="recoup_sk_..." # already set in Recoup sandboxes -export RECOUP_API="https://api.recoupable.com/api" -``` - -No key? Get one instantly (no dashboard, no email verification): - -```bash -export RECOUP_API_KEY=$(curl -s -X POST "https://api.recoupable.com/api/agents/signup" \ - -H "Content-Type: application/json" \ - -d '{"email":"agent+'$(date +%s)-$RANDOM'@recoupable.com"}' | jq -r .api_key) -``` - -Reference docs: - -## Decision tree - -Start here based on what the user asks: - -- **"How is [artist] doing?"** → `metrics?source=spotify` + `insights` (+ `milestones` for recent events) -- **"Research [artist] for me"** → `profile` → parallel(`metrics`, `audience`, `similar`, `playlists`, `tracks`) → `web` or `deep` for narrative → synthesize -- **"Who should I pitch to?"** → `similar?audience=high&genre=high` → `playlists` on each peer → find playlists peers are on and your artist isn't -- **"Where is the audience?"** → `audience?platform=instagram|tiktok|youtube` (city-level geography is no longer a direct endpoint — use `web`/`deep` for it) -- **"Find me [people]"** → `POST /research/people` with `{ "query": "A&R reps at Atlantic" }` -- **"Tell me about [entity]"** → `POST /research/enrich` for structured data, or `POST /research/deep` for cited narrative -- **"What does this page say?"** → `POST /research/extract` with `{ "urls": [...] }` -- **"Where does this track get played?"** → search `type=tracks` for the track `id` → `track/playlists?id=` -- **"Find emerging / related artists"** → `similar` on a known anchor artist, then `metrics` to size each candidate (there is no `discover` or `charts` endpoint anymore) - -If none match, start with `POST /research/web`. - -Before researching: check if the artist already has a workspace `context/artist.md` — don't re-research what's known. - -## Endpoints (quick reference) - -Full curl examples, filter flag rules, latency budgets, and platform source enums live in **`references/endpoints.md`**. - -| Endpoint | Returns | -| -------- | ------- | -| `GET /research?q=&type=artists\|tracks\|labels` | search → provider IDs | -| `GET /research/lookup?url=` or `?spotifyId=` | Spotify URL/ID → `artist_info` | -| `GET /research/profile?artist=` or `?id=` | bio, genres, country, links, related artists (**no** counts) | -| `GET /research/metrics?artist=&source=` | current snapshot stats (followers / listeners / playlists) | -| `GET /research/audience?artist=&platform=instagram\|tiktok\|youtube` | age / gender / country | -| `GET /research/similar?artist=&audience=&genre=&mood=&musicality=` | peer artists (flat list, no scores) | -| `GET /research/playlists?artist=&platform=&status=` | artist playlist placements (flat, single-shot) | -| `GET /research/tracks?artist=` | catalog | -| `GET /research/career?artist=` | career activity feed | -| `GET /research/insights?artist=` | AI observations | -| `GET /research/milestones?artist=` | activity feed (playlist adds, chart entries) | -| `GET /research/urls?artist=` | social / streaming URLs | -| `GET /research/albums?artist_id=` | albums (provider artist ID, 5 credits) | -| `GET /research/track?id=` | track detail + audio analysis (provider track ID, 5 credits) | -| `GET /research/track/playlists?id=` (filter flags) | playlists featuring a track (5 credits, paginates) | -| `POST /research/web` | web search (~seconds) | -| `POST /research/deep` | comprehensive cited report (~2+ min) | -| `POST /research/people` | industry people search (~seconds-tens) | -| `POST /research/extract` | URL → markdown excerpts (~seconds-tens) | -| `POST /research/enrich` | structured extraction with schema (~60-90s) | - -## Critical gotchas - -These are the failure modes that will eat your time. Full rationale in `references/endpoints.md` and `references/response-shapes.md`. - -- **Provider IDs are short alphanumeric strings** (`wjcgfd9i`), not numeric Chartmetric IDs. Get them via `GET /research?q=...&type=...`. There is **no `beta` flag and no `match_strength` score** anymore — search returns `results[]`; disambiguate by `name` / `avatar` / `site_url` (and `release_date` / `artists[]` for tracks). Empty `results` → fall through to graceful degradation. -- **Removed endpoints (these all 404 — don't call them):** `cities`, `charts`, `discover`, `genres`, `festivals`, `radio`, `venues`, `rank`, `instagram-posts`, `playlist` (singular), `curator`. Geography now comes from `audience` (+ `web`/`deep`); discovery from `similar` + `web`. -- **`/research/profile` carries NO follower/listener counts** (and no `career_stage`). Those numbers live in `/research/metrics`, which returns a **current snapshot**, not a time series. -- **Most artist endpoints accept `artist=` OR `id=`.** Use the name for convenience; use the id (from search/lookup) for an exact entity. ID-only detail endpoints (`albums`, `track`, `track/playlists`) need the provider id. -- **Playlist filter flags live on `track/playlists` only, and are exclusive when set.** `?editorial=true` alone returns ONLY editorials; to get editorial *plus* the defaults pass all four: `&editorial=true&indie=true&majorCurator=true&popularIndie=true`. The artist-level `/research/playlists` has **no** filter flags and returns a flat `placements[]` snapshot (no `offset` paging). -- **`/research/track` has NO per-song TikTok fields** (`tt_uses`, `tt_views`, etc.) — the API does not expose per-song TikTok counts. Don't look for them and don't fabricate them. -- **`/research/metrics` uses `youtube_channel` or `youtube_artist`**, not plain `youtube`. `/research/audience?platform=` accepts only `instagram | tiktok | youtube`. -- **`/research/enrich` schemas must include `"type":"object"` at the top level.** The endpoint rejects schemas without it. -- **POST endpoints have real latency.** `/enrich` 60–90s, `/deep` 2+ min, others seconds-to-tens. Set client timeouts to ≥3 min for `/enrich` and `/deep` or you'll falsely abort successful calls. -- **Empty arrays are legit.** `/research/milestones` and `/research/audience` commonly return `[]` even for established artists — that's not an error; don't retry or escalate, degrade instead. -- **Cold-cache calls can return a transient error/`202` — retry before giving up.** The research API caches provider data, so a *cold* GET (`career`, `playlists`, `metrics`, etc.) can come back `status: "error"` or `202 { state: "refresh_pending" }` and then succeed on the very next call. Retry once or twice with a short sleep before treating it as "no data" — a single transient error is **not** proof the artist has no data. (Distinct from a genuinely empty `[]` above, which is stable across retries.) -- **Don't guess field names.** Importance on activity feeds is `activity_tier` (integer, lower = more significant), not a star rating; placements are flat (`placements[].playlist_name`), and `followers_total` is a human-readable string like `"34.3M"`. Full real shapes in `references/response-shapes.md`. - -## Graceful degradation - -Fall through to web research if **any** of these are true: - -- `GET /research?q=...` returns `{ results: [] }` -- `GET /research/lookup?url=...` returns non-200 -- `/research/profile` comes back with mostly `null` fields - -Then: - -1. `POST /research/web` — ranked results -2. `POST /research/enrich` — structured facts (~60–90s) -3. `POST /research/deep` — cited narrative (~2+ min) - -For very emerging artists, the provider may have no structured data — web + enrich + deep is the fallback. - -## How to use the data - -Don't dump raw JSON. Combine endpoints, draw conclusions, save to the artist workspace if there is one. Interpretation rules of thumb (follower:listener ratios, audience-vs-placement mismatches, snapshot diffing over time) and end-to-end synthesis patterns (geographic strategy, playlist gap analysis, platform pipeline, etc.) are in **`references/workflows.md`**. - -## References - -These reference files ship alongside this skill, in its own `references/` folder: - -- **`references/endpoints.md`** — full curl examples per endpoint, playlist filter / pagination semantics, latency budgets, platform source enums -- **`references/response-shapes.md`** — actual JSON shapes for `/search`, `/profile`, `/metrics`, `/similar`, `/playlists`, `/track`, activity feeds, plus field-name gotchas -- **`references/workflows.md`** — interpretation cheat sheet, synthesis patterns, multi-step workflow chains, and where to save research output diff --git a/skills/music-industry-research/references/endpoints.md b/skills/music-industry-research/references/endpoints.md deleted file mode 100644 index e6bf7bb..0000000 --- a/skills/music-industry-research/references/endpoints.md +++ /dev/null @@ -1,317 +0,0 @@ -# Endpoints reference - -Full curl examples for every production `/api/research/*` endpoint, plus -parameter rules, response shapes, latency budgets, and platform source enums. - -These are the **current production endpoints** documented at - (see the `/api-reference/research/*` pages). -The research API is backed by **Songstats**, so entity IDs are short -alphanumeric strings like `wjcgfd9i` (artists) or `1ik97vot` (tracks) — **not** -the long numeric Chartmetric IDs older versions of this skill used. - -All examples assume: - -```bash -export RECOUP_API_KEY="recoup_sk_..." -export RECOUP_API="https://api.recoupable.com/api" -``` - -No key? Get one instantly (no dashboard, no email verification): - -```bash -export RECOUP_API_KEY=$(curl -s -X POST "https://api.recoupable.com/api/agents/signup" \ - -H "Content-Type: application/json" \ - -d '{"email":"agent+'$(date +%s)-$RANDOM'@recoupable.com"}' | jq -r .api_key) -``` - ---- - -## 1. Search (the discovery primitive) - -`GET /research` — find a provider `id` to pass into the ID-based lookups -(`albums`, `track`, `track/playlists`). Most artist endpoints accept a plain -`artist=` name, so you only need search when you want an exact ID or are -disambiguating between same-named entities. - -```bash -curl -s "$RECOUP_API/research?q=Drake&type=artists" \ - -H "x-api-key: $RECOUP_API_KEY" | jq -``` - -Query params: - -- `q` (**required**) — a name (`Drake`, `Flowers`) **or** a streaming URL - (`https://open.spotify.com/artist/...`). -- `type` — `artists` (default), `tracks`, or `labels`. **These are the only - three valid types.** (Older versions accepted `albums`/`playlists`/`curators`/ - `stations` — those no longer exist.) -- `limit` — max results (default `10`). -- `offset` — pagination offset (default `0`). - -Response (`type=artists`): - -```jsonc -{ - "status": "success", - "results": [ - { - "id": "wjcgfd9i", // provider-neutral ID — pass into detail endpoints - "songstats_artist_id": "wjcgfd9i", - "name": "Drake", - "avatar": "https://i.scdn.co/image/...", - "site_url": "https://songstats.com/artist/wjcgfd9i/drake" - } - // ... - ] -} -``` - -`type=tracks` results carry `songstats_track_id`, `release_date`, `is_remix`, -and an `artists[]` array; `id` is the track ID for `/research/track`. - -> **No `beta`, no `platforms`, no `match_strength`.** The old beta engine and -> its `match_strength` scoring are gone. Disambiguate by reading `name`, -> `avatar`, `site_url`, and (for tracks) `release_date`/`artists[]` on the -> results instead. When `results` is empty, fall through to Graceful -> Degradation (web → enrich → deep). - ---- - -## 2. Artist endpoints (accept `artist=` name **or** `id=` provider ID) - -Every endpoint in this section takes **either** `artist=` **or** -`id=`. Use the name for convenience; use the `id` (from -search/lookup) when you need an exact entity. - -```bash -# URL → artist resolution (Spotify URL or Spotify ID only) -curl -s "$RECOUP_API/research/lookup?url=https://open.spotify.com/artist/3TVXtAsR1Inumwj472S9r4" \ - -H "x-api-key: $RECOUP_API_KEY" | jq '.artist_info.name' -curl -s "$RECOUP_API/research/lookup?spotifyId=3TVXtAsR1Inumwj472S9r4" \ - -H "x-api-key: $RECOUP_API_KEY" | jq - -# Profile — name, bio, genres, country, platform links, related artists -# NOTE: profile carries NO follower/listener counts. Use /metrics for those. -curl -s "$RECOUP_API/research/profile?artist=Drake" -H "x-api-key: $RECOUP_API_KEY" | jq '.artist_info' - -# Platform metrics — current snapshot stats for one source (see source enum below) -curl -s "$RECOUP_API/research/metrics?artist=Drake&source=spotify" -H "x-api-key: $RECOUP_API_KEY" | jq '.stats' - -# Audience demographics — platform: instagram | tiktok | youtube (default instagram) -curl -s "$RECOUP_API/research/audience?artist=Drake&platform=tiktok" -H "x-api-key: $RECOUP_API_KEY" | jq - -# Similar artists (competitors, collab targets) — weighted by audience/genre/mood/musicality -curl -s "$RECOUP_API/research/similar?artist=Drake&audience=high&genre=high&limit=20" -H "x-api-key: $RECOUP_API_KEY" | jq '.artists' - -# Playlist placements — see "Playlist semantics" below -curl -s "$RECOUP_API/research/playlists?artist=Drake&platform=spotify&status=current" -H "x-api-key: $RECOUP_API_KEY" | jq '.placements' - -# All tracks (catalog) -curl -s "$RECOUP_API/research/tracks?artist=Drake" -H "x-api-key: $RECOUP_API_KEY" | jq '.tracks' - -# Career activity feed -curl -s "$RECOUP_API/research/career?artist=Drake" -H "x-api-key: $RECOUP_API_KEY" | jq '.career' - -# AI-surfaced observations -curl -s "$RECOUP_API/research/insights?artist=Drake" -H "x-api-key: $RECOUP_API_KEY" | jq '.insights' - -# Activity feed — playlist adds, chart entries, notable events -curl -s "$RECOUP_API/research/milestones?artist=Drake" -H "x-api-key: $RECOUP_API_KEY" | jq '.milestones' - -# Social + streaming URLs -curl -s "$RECOUP_API/research/urls?artist=Drake" -H "x-api-key: $RECOUP_API_KEY" | jq '.urls' -``` - ---- - -## 3. ID-based detail endpoints - -Discover the `id` via `GET /research?q=...&type=...` first. - -```bash -# Albums — note: artist_id (provider ID), NOT name. is_primary=true (default) excludes features/compilations -curl -s "$RECOUP_API/research/albums?artist_id=wjcgfd9i&is_primary=true&limit=50" -H "x-api-key: $RECOUP_API_KEY" | jq '.albums' - -# Full track metadata + audio analysis (id from search type=tracks) -curl -s "$RECOUP_API/research/track?id=1ik97vot" -H "x-api-key: $RECOUP_API_KEY" | jq '.track_info' - -# Playlists a specific track appears on (5 credits per call, paginates) -curl -s "$RECOUP_API/research/track/playlists?id=1ik97vot&platform=spotify&status=current" \ - -H "x-api-key: $RECOUP_API_KEY" | jq '.placements' -``` - -`track/playlists` also accepts `q=` + optional `artist=` in place of `id=` for -name-based lookup, plus `status`, `since`, `until`, `limit`, `offset`, `sort`, -and the filter flags documented below. - -> **There is no `playlist` (singular), `curator`, `albums-by-name`, `cities`, -> `charts`, `discover`, `genres`, `festivals`, `radio`, `venues`, -> `instagram-posts`, or `rank` endpoint.** All of these returned `404` in -> production — they were removed in the Songstats migration. Don't call them. - ---- - -## 4. Web intelligence (POST) - -All POSTs send `Content-Type: application/json` + `x-api-key`. - -```bash -# Web search — ranked results with snippets -curl -s -X POST "$RECOUP_API/research/web" \ - -H "x-api-key: $RECOUP_API_KEY" -H "Content-Type: application/json" \ - -d '{"query":"Drake brand partnerships 2026","max_results":10,"country":"US"}' | jq - -# Deep research — comprehensive multi-source report with citations -curl -s -X POST "$RECOUP_API/research/deep" \ - -H "x-api-key: $RECOUP_API_KEY" -H "Content-Type: application/json" \ - -d '{"query":"Full deep dive on Drake: label relationships, brand deals, tour grosses, cultural moments in the last 18 months"}' | jq - -# People search — multi-source profiles (LinkedIn etc.) -curl -s -X POST "$RECOUP_API/research/people" \ - -H "x-api-key: $RECOUP_API_KEY" -H "Content-Type: application/json" \ - -d '{"query":"A&R reps at Atlantic Records","num_results":10}' | jq - -# URL extraction — up to 10 URLs, objective-targeted excerpts or full_content -curl -s -X POST "$RECOUP_API/research/extract" \ - -H "x-api-key: $RECOUP_API_KEY" -H "Content-Type: application/json" \ - -d '{"urls":["https://example.com/article"],"objective":"tour dates","full_content":false}' | jq - -# Structured enrichment — schema MUST include "type":"object" at the top level -curl -s -X POST "$RECOUP_API/research/enrich" \ - -H "x-api-key: $RECOUP_API_KEY" -H "Content-Type: application/json" \ - -d '{ - "input":"Drake rapper from Toronto", - "schema":{"type":"object","properties":{"label":{"type":"string"},"manager":{"type":"string"}}}, - "processor":"core" - }' | jq -``` - -Request-body notes: - -- `web` — `query` (required), `max_results` (1–20, default 10), `country` (2-letter ISO). -- `deep` — `query` (required) only. -- `people` — `query` (required), `num_results` (1–100, default 10). -- `extract` — `urls` (required, 1–10), `objective` (optional, ≤3000 chars), `full_content` (default `false`). -- `enrich` — `input` (required), `schema` (required, **must have `"type":"object"` at the top level**), `processor` (`base` default / `core` / `ultra`). - -### Latency — do not abort early - -The POST endpoints do multi-source web browsing and take real wall-clock time: - -| Endpoint | Typical latency | -| -------- | --------------- | -| `/research/enrich` | **60–90 seconds** (`core`; `ultra` longer) | -| `/research/deep` | **2+ minutes** for comprehensive cited reports | -| `/research/people`, `/research/web`, `/research/extract` | seconds to tens of seconds | - -If you wrap these with a default 30s client timeout, they'll appear "hung" and -you'll abort successful calls. **Set client timeouts to ≥3 minutes for -`/enrich` and `/deep`**, or stream / queue them if the UX allows. - ---- - -## Playlist semantics - -Two endpoints return playlists. They behave differently — read this before -calling either. - -### `/research/playlists` (artist-level) - -Returns the artist's current/past playlist placements. Parameters: - -- `artist=` or `id=` — the artist -- `platform` — `spotify` (default), `applemusic`, `deezer`, `amazon`, `youtube` -- `status` — `current` (default) or `past` -- `limit` — default `20` - -**No editorial/indie/curator filter flags here** (those live on the -track-level endpoint). Each placement: - -```jsonc -{ - "playlist_id": "37i9dQZF1DXcBWIGoYBM5M", // platform playlist ID - "playlist_name": "Today's Top Hits", - "external_url": "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M", - "followers_total": "34.3M", // human-readable string, e.g. "34.3M" - "image_url": "https://i.scdn.co/image/..." -} -``` - -### `/research/track/playlists` (track-level) - -Returns playlists featuring a specific track. Discover the track `id` via -`GET /research?q=...&type=tracks`, or pass `q=` + optional `artist=`. **Costs 5 -credits per request.** Parameters: `id` or `q`(+`artist`), `platform` -(`spotify` default / `applemusic` / `deezer` / `amazon`), `status` -(`current`/`past`), `limit`, `offset`, `since`, `until`, `sort`, plus filter -flags. - -**Filter flags (track-level only):** `editorial`, `indie`, `majorCurator`, -`popularIndie`, `personalized`, `chart`, `newMusicFriday`, `thisIs`, `radio`, -`brand`. - -**Default with no flags:** `editorial + indie + majorCurator + popularIndie = -true`. **The moment you pass ANY flag, the unset ones flip to `false`** — -`?editorial=true` means "ONLY editorial," not "editorial in addition to the -defaults." To get editorial *plus* the defaults, pass all four: -`?editorial=true&indie=true&majorCurator=true&popularIndie=true`. - -| You want | Query | -| -------- | ----- | -| Editorial **plus** curator/indie | `?editorial=true&indie=true&majorCurator=true&popularIndie=true` | -| Only editorial | `?editorial=true` | -| Default categories (curator/indie mix) | (no flags) | -| Everything | pass all 10 flags `=true` | - -Paginate with `offset=0, 100, 200, …` until `placements` is empty. - ---- - -## Platform sources (for `/research/metrics?source=`) - -The endpoint **accepts 16 `source` values**, but only some actually carry data -(verified live against a major artist — don't assume "accepted" means "populated"): - -- **Return data (10):** `spotify`, `instagram`, `tiktok`, `twitter`, `facebook`, - `youtube_channel`, `youtube_artist`, `soundcloud`, `deezer`, `bandsintown`. -- **Accepted but usually empty (6):** `twitch`, `line`, `melon`, `wikipedia` - (inert — the current provider doesn't supply them), plus `radio` / `sxm` (only - populated for radio-active artists). Don't burn a call expecting data here - unless you have a reason to. - -Two gotchas: - -- For `metrics`, **YouTube uses `youtube_channel` or `youtube_artist`** — not - plain `youtube`. -- `/research/audience` is a different endpoint with its own narrower enum: - `platform=instagram|tiktok|youtube` (default `instagram`). Do **not** send - `youtube_channel` there. - -A `metrics` call may return `202` with `{ status: "pending", state: -"refresh_pending" }` while the provider refreshes. Retry the same call shortly. - -> **Provider gap (not yet queryable).** The underlying provider (Songstats) also -> supplies `apple_music`, `amazon`, `shazam`, `itunes`, `tidal`, `beatport`, -> `traxsource`, and `1001tracklists` data — and you'll even see some of these -> `source` values appear inside `/research/career` and `/research/milestones` -> activity feeds — but `/research/metrics` currently **rejects them** -> (`status: "error"`). They are not available as metric snapshots today; don't -> pass them to `metrics`. (Tracked as an API-side gap to expose.) - ---- - -## Credit costs - -Some endpoints cost more credits than others. If the account returns -`{ "error": "insufficient_credits" }`, the response includes `remaining_credits`, -`required_credits`, and a `checkoutUrl` for the user to add credits. - -| Cost tier | Endpoints | -| --------- | --------- | -| Free / 1 credit | `search`, `profile`, `metrics`, `audience`, `similar`, `playlists`, `insights`, `tracks`, `career`, `milestones`, `urls`, `web` | -| 5 credits | `lookup`, `albums`, `track`, `track/playlists` | -| Higher (varies) | `enrich`, `deep`, `people`, `extract` (POST endpoints) | - -Credit costs are approximate and may change. When you get a `402` or -`insufficient_credits` error, surface it to the user with the checkout link — -don't silently fail or retry. diff --git a/skills/music-industry-research/references/response-shapes.md b/skills/music-industry-research/references/response-shapes.md deleted file mode 100644 index 2ea35b1..0000000 --- a/skills/music-industry-research/references/response-shapes.md +++ /dev/null @@ -1,260 +0,0 @@ -# Response shapes (what the fields actually are) - -Don't guess field names. The research API is backed by **Songstats**, and -responses carry `additionalProperties: true` (extra fields may pass through), -but these are the fields that exist and matter. If you're looking for a field -that isn't here, it probably doesn't exist — dump the raw response once with -`jq '.[0] | keys'` before coding against it. - -**IDs are short alphanumeric strings** (`wjcgfd9i`, `1ik97vot`), not numeric -Chartmetric IDs. **`career_stage`, `recent_momentum`, `match_strength`, -`cm_statistics`, and `peak_position` no longer exist** anywhere in the API. - -**jq parsing note:** Some responses (especially `/research/profile`) may contain -control characters in text fields (e.g. bio). If `jq` fails with -"Invalid string: control characters", pipe through `tr -d '\000-\011\013\014\016-\037'` -first, or use Python's `json.loads()` which handles them. - -## `/research` (search) → `results[].*` - -```jsonc -{ - "id": "wjcgfd9i", // provider-neutral ID → pass to detail endpoints - "songstats_artist_id": "wjcgfd9i", // (type=tracks → songstats_track_id) - "name": "Drake", - "avatar": "https://i.scdn.co/image/...", - "site_url": "https://songstats.com/artist/wjcgfd9i/drake" - // type=tracks also: release_date, is_remix, artists[], labels[] -} -``` - -There is **no** `match_strength` and no relevance score. Disambiguate by `name`, -`avatar`, `site_url`, and (tracks) `release_date` / `artists[]`. - -## `/research/profile` and `/research/lookup` → `artist_info.*` - -Both return the same `artist_info` object. **There are no follower/listener -counts on profile** — those come from `/research/metrics`. - -```jsonc -{ - "status": "success", - "result": "success", - "message": "Data Retrieved.", - "artist_info": { - "name": "Drake", - "avatar": "https://i.scdn.co/image/...", - "site_url": "https://songstats.com/artist/...", - "country": "US", // ISO code, may be null - "bio": "A year or so after...", // may be null - "genres": ["Rap", "Hip Hop", "Pop Rap"], - "links": [ - { "source": "spotify", "external_id": "3TVXtAsR1Inumwj472S9r4", - "url": "https://open.spotify.com/artist/3TVXtAsR1Inumwj472S9r4" } - // beatport, traxsource, apple_music, etc. - ], - "related_artists": [ - { "songstats_artist_id": "0rtly9qv", "name": "Tory Lanez", - "avatar": "...", "site_url": "..." } - ] - } -} -``` - -## `/research/metrics` → `stats[].data` (CURRENT snapshot, not a time series) - -`/research/metrics?source=spotify` returns **current snapshot counters**, not a -historical time series. This is where follower/listener/playlist numbers live. - -```jsonc -{ - "status": "success", - "source_ids": ["spotify"], - "artist_info": { "songstats_artist_id": "wjcgfd9i", "name": "Drake", "avatar": "...", "site_url": "..." }, - "stats": [ - { - "source": "spotify", - "data": { - "monthly_listeners_current": 99477872, - "followers_total": 112185098, - "popularity_current": 100, - "streams_total": 137929083971, - "playlists_current": 176085, - "playlists_total": 214818, - "playlists_editorial_current": 568, - "playlists_editorial_total": 2102, - "playlist_reach_current": 993868411, - "playlist_reach_total": 1939843597, - "charts_current": 179, - "charts_total": 341, - "charted_tracks_current": 66, - "charted_tracks_total": 399, - "charted_cities_total": 135, - "charted_countries_total": 74 - } - } - ] -} -``` - -The `data` keys vary by `source`. For TikTok you'll see follower/like/video -counts; for instagram follower/engagement counts; etc. Always `jq -'.stats[0].data | keys'` once for an unfamiliar source. A `202` -`{ status: "pending", state: "refresh_pending" }` means retry shortly. - -## `/research/similar` → `artists[].*` (flat list, no scores) - -```jsonc -{ - "status": "success", - "artists": [ - { "id": "0rtly9qv", "songstats_artist_id": "0rtly9qv", - "name": "Tory Lanez", "avatar": "...", "site_url": "..." } - ] -} -``` - -**No similarity score, no `career_stage`, no `recent_momentum`, no platform -counts.** It's a ranked-by-relevance list of artist references. To judge a -peer's size/stage, call `/research/metrics` on that peer. Weight the match with -`audience`, `genre`, `mood`, `musicality` (each `high|medium|low`, default -`medium`). - -## `/research/playlists` → `placements[].*` (FLAT, not nested) - -```jsonc -{ - "status": "success", - "placements": [ - { - "playlist_id": "37i9dQZF1DXcBWIGoYBM5M", // platform playlist ID - "playlist_name": "Today's Top Hits", - "external_url": "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M", - "followers_total": "34.3M", // human-readable STRING - "image_url": "https://i.scdn.co/image/..." - } - ] -} -``` - -`followers_total` is a formatted string (`"34.3M"`), not an integer. The shape -is flat — there is no nested `playlist`/`track` wrapper and no `editorial` -boolean on artist-level placements. - -## `/research/track/playlists` → `placements[]` - -`{ status, placements: [...] }`. Placement shape varies by platform; each entry -is the provider's raw playlist object. `jq '.placements[0] | keys'` once. - -## `/research/tracks` → `tracks[]` and `/research/albums` → `albums[]` (catalog items) - -```jsonc -{ - "songstats_track_id": "p8or4a5h", // tracks; albums omit this - "title": "Janice STFU (HILLS Remix)", - "avatar": "https://data.songstats.com/images/...", - "release_date": "2026-05-29", // may be null - "site_url": "https://songstats.com/track/...", - "isrcs": [], - "artists": [{ "name": "Drake", "songstats_artist_id": "wjcgfd9i" }] -} -``` - -The catalog is **unfiltered** — high-catalog artists include remixes and -feature appearances. There is **no per-track popularity, stream, or TikTok -count** in this list. - -## `/research/track` → `track_info` + `audio_analysis` - -```jsonc -{ - "status": "success", - "track_info": { - "title": "...", "avatar": "...", "site_url": "...", - "release_date": "2024-02-25", - "artists": [{ "name": "..." }], - "is_remix": false, - "collaborators": [{ "name": "...", "roles": ["producer"] }], - "labels": [ /* objects */ ], - "distributors": [{ "name": "..." }], - "genres": ["..."], - "links": [{ "source": "spotify", "external_id": "...", "url": "...", "isrc": "..." }] - }, - "audio_analysis": [ - { "key": "acousticness", "value": "0.12" }, - { "key": "tempo", "value": "134" } - // ~13 audio features as key/value strings - ] -} -``` - -**There are NO TikTok fields** (`tt_uses`, `tt_views`, etc.) on track detail — -the API does not expose per-song TikTok counts. Don't look for them. - -## `/research/career`, `/research/insights`, `/research/milestones` → activity feeds - -All three return an array of the same `activity` shape, keyed `career`, -`insights`, and `milestones` respectively. - -```jsonc -{ - "status": "success", - "milestones": [ - { - "source": "spotify", - "activity_text": "Added to Today's Top Hits", - "activity_type": "playlist", // e.g. playlist, chart - "activity_date": "2026-06-04", - "activity_url": "...", // may be null - "activity_avatar": "...", // may be null - "activity_tier": 1, // importance: lower = more significant - "track_info": { "title": "...", "release_date": "...", "artists": [{ "name": "..." }] } - } - ] -} -``` - -Importance is `activity_tier` (an integer, lower = bigger) — **not** a star -rating. An empty array (`{ status: "success", milestones: [] }`) is legitimate; -many artists simply have no recent discrete events. Don't retry or escalate on -empty — fall back to `/research/insights` or `/research/career`. - -## `/research/audience` → `audience[]` - -```jsonc -{ "status": "success", "audience": [ /* per-platform demographic objects */ ], - "artist_info": { ... }, "source_ids": ["tiktok"] } -``` - -`audience` is frequently `[]` for an artist/platform the provider has no data -for. Empty is not an error — fall back to another platform or to cities-via-web -research. - -## `/research/urls` → `urls[]` - -```jsonc -{ "status": "success", - "urls": [ { "domain": "spotify", "url": "https://open.spotify.com/artist/..." }, - { "domain": "instagram", "url": "..." } ] } -``` - -Each entry is a `{ domain, url }` pair (it also includes pseudo-entries like -`avatar` and `site_url`). - -## POST responses - -- `/research/web` → `{ status, results: [{ title, url, snippet, date, last_updated }], formatted }` -- `/research/deep` → `{ status, content (markdown), citations: [url, ...] }` -- `/research/people` → `{ status, results: [{ title, url, id, publishedDate, author, highlights[], summary }] }` -- `/research/extract` → `{ status, results: [{ url, title, publish_date, excerpts[], full_content }], errors[] }` -- `/research/enrich` → `{ status, output: { ...your schema... }, citations: [{ url, title, field }] }` - -## Error shape (all endpoints) - -```jsonc -{ "status": "error", "error": "Missing required parameter: artist" } -``` - -`501` with `Request failed with status 501` means the configured data source -doesn't support that endpoint/shape — treat it like "no data" and degrade -gracefully. diff --git a/skills/music-industry-research/references/workflows.md b/skills/music-industry-research/references/workflows.md deleted file mode 100644 index 9ade1c4..0000000 --- a/skills/music-industry-research/references/workflows.md +++ /dev/null @@ -1,657 +0,0 @@ -# Research workflows, interpretation, and synthesis - -Multi-step workflows that chain `/api/research/*` endpoints to answer strategic -questions, plus the interpretation rules of thumb and cross-cutting synthesis -patterns to apply once you have the data. - -The research API is backed by **Songstats** — IDs are short alphanumeric -strings, and several endpoints from older versions (`cities`, `charts`, -`discover`, `genres`, `festivals`, `radio`, `venues`, `rank`, -`instagram-posts`, `playlist`/`curator` detail) **no longer exist**. The -workflows below only use endpoints that are live in production. - -All examples assume: - -```bash -export RECOUP_API_KEY="recoup_sk_..." -export RECOUP_API="https://api.recoupable.com/api" -AUTH="x-api-key: $RECOUP_API_KEY" -``` - ---- - -## Interpretation cheat sheet - -Raw numbers are noise without interpretation. Heuristics for each data type: - -**Metrics** (`/research/metrics?source=spotify` → `stats[0].data`): - -- `followers_total ÷ monthly_listeners_current` above ~20% = dedicated fan base - (they follow, not just stream) -- `popularity_current` near 100 = strong algorithmic favor -- `playlists_editorial_current` low relative to `monthly_listeners_current` = - under-playlisted (editorial pitch opportunity) -- Compare `*_current` vs `*_total` (e.g. `playlists_current` vs - `playlists_total`) to see what's active now vs all-time -- These are **current snapshots**, not time series — to track change over time, - store today's snapshot and diff against a prior one (persist daily snapshots - to the artist workspace and compare) - -**Similar artists** (`/research/similar` → `artists[]`): - -- It's a flat, ranked list of artist references — **no scores, no career stage, - no momentum**. To size up a peer, call `/research/metrics` on them. -- Weight the match with `audience` / `genre` / `mood` / `musicality` - (`high|medium|low`). `audience=high` for shared-fanbase peers; `musicality=high` - for sonic look-alikes (better for discovery of smaller artists). -- Peers with playlists you're NOT on = pitch targets. - -**Playlists** (`/research/playlists` → `placements[]`): - -- `followers_total` is a formatted string (`"34.3M"`), not an integer — parse it - if you need to sort numerically. -- Artist-level placements have **no editorial flag**; for editorial-only - filtering use `/research/track/playlists?...&editorial=true` per track, or read - `playlists_editorial_current` from `/research/metrics`. -- `status=past` placements that dropped off = re-pitch opportunities. - -**Audience** (`/research/audience?platform=` → `audience[]`): - -- The only geographic/demographic source now (Spotify city data was removed). - Country breakdown here is your geography signal. -- Gender skew → content strategy (visual style, messaging). -- Age concentration → platform priority (Gen Z = TikTok, 25–34 = Instagram). -- Often `[]` for smaller artists — fall back to another platform or web research. - -**Activity feeds** (`/research/career`, `/research/insights`, `/research/milestones`): - -- Each entry has `activity_tier` (integer, **lower = more significant**) — sort - ascending to surface the biggest events. There is no star rating. -- Empty arrays are legitimate; don't retry. Cross-read the three feeds for - narrative. - ---- - -## Cross-cutting synthesis patterns - -Don't dump raw JSON. Combine endpoints and draw conclusions: - -- **Geographic strategy:** `audience?platform=instagram` + `audience?platform=tiktok` - → "IG audience is 80% US but TikTok is 35% Brazil — the Brazilian fan base - isn't being served with localized content." -- **Playlist gap analysis:** `similar` → `playlists` on each peer → "5 of your 10 - peers are on 'RapCaviar', you're not. Top pitch target." -- **Platform pipeline:** `metrics?source=tiktok` + `metrics?source=spotify` → - "TikTok followers far ahead of Spotify conversion. Virality isn't translating. - Add Spotify-specific CTAs to TikTok content." -- **Editorial gap:** `metrics?source=spotify` (`playlists_editorial_current`) + - `playlists` → "568 editorial placements for 99M listeners is healthy; the gap is - algorithmic, not editorial." -- **Catalog momentum:** `tracks` + `milestones` → identify recent playlist/chart - events tied to specific tracks the artist could amplify. - ---- - -## Saving research - -If working in an artist workspace, save research results to `research/` with timestamps: - -``` -research/artist-intel-2026-04-17.md -``` - -Don't overwrite `context/artist.md` with research data. Static context (who the -artist IS) is separate from dynamic research (how they're performing NOW). If -the research reveals something that should update the static profile, suggest it -— don't auto-update. - ---- - -## 1. Playlist Pitching Intelligence - -**Question:** "Which playlists should I pitch to?" - -```bash -# 1. Find similar artists slightly bigger than yours (good benchmarks) -curl -s "$RECOUP_API/research/similar?artist={ARTIST}&audience=high&genre=high&limit=50" -H "$AUTH" - -# 2. For each similar artist, get their current playlist placements -curl -s "$RECOUP_API/research/playlists?artist={similar_artist}&platform=spotify&status=current" -H "$AUTH" - -# 3. Look for overlap — playlists hosting multiple similar artists that yours isn't on - -# 4. For per-track editorial detail, resolve a track id then page the track-level endpoint -curl -s "$RECOUP_API/research?q={TRACK_NAME}&type=tracks" -H "$AUTH" # get track id -curl -s "$RECOUP_API/research/track/playlists?id={track_id}&editorial=true&indie=true&majorCurator=true&popularIndie=true" -H "$AUTH" - -# 5. Check if the target artist was ever on these playlists before -curl -s "$RECOUP_API/research/playlists?artist={ARTIST}&status=past" -H "$AUTH" -``` - -**What to synthesize:** Playlists that already host similar artists but haven't -added yours yet. Prioritize playlists hosting 2+ similar artists — they're the -warmest targets. - ---- - -## 2. TikTok-to-Spotify Pipeline Analysis - -**Question:** "Is TikTok virality translating to Spotify growth?" - -```bash -# 1. TikTok metrics snapshot -curl -s "$RECOUP_API/research/metrics?artist={ARTIST}&source=tiktok" -H "$AUTH" - -# 2. Spotify metrics snapshot -curl -s "$RECOUP_API/research/metrics?artist={ARTIST}&source=spotify" -H "$AUTH" - -# 3. TikTok audience demographics -curl -s "$RECOUP_API/research/audience?artist={ARTIST}&platform=tiktok" -H "$AUTH" - -# 4. Instagram audience for geographic cross-check -curl -s "$RECOUP_API/research/audience?artist={ARTIST}&platform=instagram" -H "$AUTH" - -# 5. Narrative context for any spike -curl -s -X POST "$RECOUP_API/research/web" -H "$AUTH" -H "Content-Type: application/json" \ - -d '{"query":"{ARTIST} viral TikTok moment 2026","max_results":10}' -``` - -**What to synthesize:** Compare TikTok scale against Spotify conversion. -Geographic mismatch between TikTok audience and Spotify audience = opportunity -(e.g. TikTok skews Brazil but Spotify skews US → Brazil is untapped). Because -metrics are snapshots, store today's reading to measure change next time. - ---- - -## 3. Geographic & Tour Strategy - -**Question:** "Where is this artist strong, and where should they tour?" - -> The dedicated `cities`, `venues`, and `festivals` endpoints were removed. -> Geography now comes from `/research/audience` country breakdowns plus web -> research for venues/festivals. - -```bash -# 1. Audience geography across platforms (country breakdown lives here now) -curl -s "$RECOUP_API/research/audience?artist={ARTIST}&platform=instagram" -H "$AUTH" -curl -s "$RECOUP_API/research/audience?artist={ARTIST}&platform=tiktok" -H "$AUTH" -curl -s "$RECOUP_API/research/audience?artist={ARTIST}&platform=youtube" -H "$AUTH" - -# 2. Similar artists (for co-headlining / routing comps) -curl -s "$RECOUP_API/research/similar?artist={ARTIST}&audience=high&limit=20" -H "$AUTH" - -# 3. Venue history, festival fit, recent touring — web/deep research -curl -s -X POST "$RECOUP_API/research/web" -H "$AUTH" -H "Content-Type: application/json" \ - -d '{"query":"{ARTIST} tour dates venues capacity 2025 2026","max_results":10}' -curl -s -X POST "$RECOUP_API/research/deep" -H "$AUTH" -H "Content-Type: application/json" \ - -d '{"query":"{ARTIST} touring history, venue sizes, and festival appearances over the last two years"}' -``` - -**What to synthesize:** Ranked markets by audience concentration, cross-checked -with where similar artists tour successfully (from web research) and the -artist's own venue history. Markets with audience but no recent touring = -expansion opportunities. - ---- - -## 4. A&R Discovery - -**Question:** "Find emerging artists in [genre] before they blow up" - -> The `discover` (filter-based) and `genres` endpoints were removed. Discovery -> now starts from a known **anchor artist** and fans out through `/similar`, -> validated with `/metrics`. Use web/deep research to find anchors or scan a -> scene. - -```bash -# 1. Pick an anchor — a known artist in the target sound. Find them, or scout via web. -curl -s "$RECOUP_API/research?q={ANCHOR_ARTIST}&type=artists" -H "$AUTH" -# (or) discover candidate names from the web: -curl -s -X POST "$RECOUP_API/research/web" -H "$AUTH" -H "Content-Type: application/json" \ - -d '{"query":"emerging {GENRE} artists to watch 2026","max_results":15}' - -# 2. Fan out to sonic look-alikes (musicality=high surfaces smaller, undiscovered acts) -curl -s "$RECOUP_API/research/similar?artist={ANCHOR_ARTIST}&musicality=high&genre=high&limit=50" -H "$AUTH" - -# 3. For each candidate, validate trajectory on two platforms (size them — similar has no metrics) -curl -s "$RECOUP_API/research/metrics?artist={candidate}&source=spotify" -H "$AUTH" -curl -s "$RECOUP_API/research/metrics?artist={candidate}&source=tiktok" -H "$AUTH" - -# 4. Editorial pickup = label interest signal -curl -s "$RECOUP_API/research/metrics?artist={candidate}&source=spotify" -H "$AUTH" # playlists_editorial_current -curl -s "$RECOUP_API/research/playlists?artist={candidate}&status=current" -H "$AUTH" - -# 5. AI-generated insights for context -curl -s "$RECOUP_API/research/insights?artist={candidate}" -H "$AUTH" -``` - -**What to synthesize:** Emerging artists with a similar sound but a smaller -audience. Since `/similar` gives no stage/score, rank candidates yourself by the -`/metrics` snapshot (lower `monthly_listeners_current` with rising editorial -pickup = the breakout window). - ---- - -## 5. Catalog Optimization - -**Question:** "Which songs should we push and where?" - -```bash -# 1. All tracks (artist-scoped) -curl -s "$RECOUP_API/research/tracks?artist={ARTIST}" -H "$AUTH" - -# 2. Albums (needs the provider artist id — get it from search) -curl -s "$RECOUP_API/research?q={ARTIST}&type=artists" -H "$AUTH" # get id -curl -s "$RECOUP_API/research/albums?artist_id={artist_id}" -H "$AUTH" - -# 3. Current playlist placements (which songs are playlisted today?) -curl -s "$RECOUP_API/research/playlists?artist={ARTIST}&status=current" -H "$AUTH" - -# 4. For deep per-track playlist coverage, resolve a track id first -curl -s "$RECOUP_API/research?q={TRACK_NAME}&type=tracks" -H "$AUTH" # get track id -curl -s "$RECOUP_API/research/track/playlists?id={track_id}&editorial=true&indie=true&majorCurator=true&popularIndie=true" -H "$AUTH" - -# 5. Track detail (genres, audio analysis, collaborators) for sound fit -curl -s "$RECOUP_API/research/track?id={track_id}" -H "$AUTH" - -# 6. Artist metrics for context -curl -s "$RECOUP_API/research/metrics?artist={ARTIST}&source=spotify" -H "$AUTH" -curl -s "$RECOUP_API/research/metrics?artist={ARTIST}&source=tiktok" -H "$AUTH" -``` - -**What to synthesize:** Track-by-track. Look for: - -- Tracks on many playlists but not converting to streams = discovery issue -- Tracks with strong audio-analysis fit for a target playlist = pitch candidates -- Old songs suddenly getting playlisted (from `/milestones`) = catalog momentum - ---- - -## 6. Competitive Roster Analysis - -**Question:** "How does our roster compare to a competitor label?" - -For each artist on your roster: - -```bash -# 1. Profile + metrics -curl -s "$RECOUP_API/research/profile?artist={your_artist}" -H "$AUTH" -curl -s "$RECOUP_API/research/metrics?artist={your_artist}&source=spotify" -H "$AUTH" - -# 2. Similar artists (to infer competitor roster) -curl -s "$RECOUP_API/research/similar?artist={your_artist}&audience=high&genre=high" -H "$AUTH" - -# 3. Playlist placements -curl -s "$RECOUP_API/research/playlists?artist={your_artist}&status=current" -H "$AUTH" -curl -s "$RECOUP_API/research/playlists?artist={competitor_artist}&status=current" -H "$AUTH" - -# 4. Audience demographics + geography -curl -s "$RECOUP_API/research/audience?artist={your_artist}&platform=instagram" -H "$AUTH" -curl -s "$RECOUP_API/research/audience?artist={competitor_artist}&platform=instagram" -H "$AUTH" - -# 5. Headline numbers come from /metrics (there is no /rank endpoint) -curl -s "$RECOUP_API/research/metrics?artist={competitor_artist}&source=spotify" -H "$AUTH" -``` - -**What to synthesize:** Side-by-side comparison of `/metrics` snapshots -(monthly listeners, followers, editorial playlists, playlist reach). Identify -where your roster under-indexes vs competitors — those are the gaps to close. - ---- - -## 7. Viral Song Autopsy - -**Question:** "Why did this song go viral? Can we replicate it?" - -```bash -# 1. Resolve + fetch track details (genres, audio analysis, collaborators) -curl -s "$RECOUP_API/research?q={TRACK_NAME}&type=tracks" -H "$AUTH" # get track id -curl -s "$RECOUP_API/research/track?id={track_id}" -H "$AUTH" - -# 2. Artist metrics snapshots -curl -s "$RECOUP_API/research/metrics?artist={ARTIST}&source=spotify" -H "$AUTH" -curl -s "$RECOUP_API/research/metrics?artist={ARTIST}&source=tiktok" -H "$AUTH" - -# 3. Playlist timeline for this track specifically (5 credits) -curl -s "$RECOUP_API/research/track/playlists?id={track_id}&status=current" -H "$AUTH" -curl -s "$RECOUP_API/research/track/playlists?id={track_id}&status=past&since=2025-01-01" -H "$AUTH" - -# 4. Artist-level activity feed — chart entries, big playlist adds -curl -s "$RECOUP_API/research/milestones?artist={ARTIST}" -H "$AUTH" - -# 5. AI insights (often mention the viral moment) -curl -s "$RECOUP_API/research/insights?artist={ARTIST}" -H "$AUTH" - -# 6. Narrative context for press / cultural framing -curl -s -X POST "$RECOUP_API/research/web" -H "$AUTH" -H "Content-Type: application/json" \ - -d '{"query":"{TRACK_NAME} {ARTIST} viral TikTok moment","max_results":10}' -``` - -**What to synthesize:** Timeline of the viral moment — which playlists amplified -it (from `track/playlists`), what the audio analysis suggests about its hook, -which audience drove sharing. The per-song TikTok *counts* aren't in the API — -get that narrative from web/deep research, not fabricated numbers. - ---- - -## 8. Market Expansion Scouting - -**Question:** "Which new markets should we focus on?" - -```bash -# 1. Platform-specific audience country breakdown (the geography source) -curl -s "$RECOUP_API/research/audience?artist={ARTIST}&platform=instagram" -H "$AUTH" -curl -s "$RECOUP_API/research/audience?artist={ARTIST}&platform=tiktok" -H "$AUTH" -curl -s "$RECOUP_API/research/audience?artist={ARTIST}&platform=youtube" -H "$AUTH" - -# 2. Similar artists and their audience geography -curl -s "$RECOUP_API/research/similar?artist={ARTIST}&genre=high&limit=10" -H "$AUTH" -# For each similar artist: -curl -s "$RECOUP_API/research/audience?artist={similar_artist}&platform=instagram" -H "$AUTH" - -# 3. Playlist coverage (curator markets as a soft regional proxy) -curl -s "$RECOUP_API/research/playlists?artist={ARTIST}&status=current" -H "$AUTH" - -# 4. Regional/scene context for a candidate market -curl -s -X POST "$RECOUP_API/research/web" -H "$AUTH" -H "Content-Type: application/json" \ - -d '{"query":"{GENRE} scene streaming growth Brazil 2026","max_results":10,"country":"BR"}' -``` - -**What to synthesize:** Markets where similar artists over-index but the target -artist is weak = expansion opportunities. Cross-reference with playlist coverage -— markets with fans but no playlist presence need pitching. - ---- - -## 9. Collaboration Finder - -**Question:** "Which artists should we collaborate with?" - -```bash -# 1. Shared fanbase -curl -s "$RECOUP_API/research/similar?artist={ARTIST}&audience=high&limit=30" -H "$AUTH" - -# 2. Genre/sound overlap -curl -s "$RECOUP_API/research/similar?artist={ARTIST}&genre=high&musicality=high" -H "$AUTH" - -# 3. Size each candidate (similar has no metrics) + playlist synergy -curl -s "$RECOUP_API/research/metrics?artist={collab_target}&source=spotify" -H "$AUTH" -curl -s "$RECOUP_API/research/playlists?artist={collab_target}&status=current" -H "$AUTH" - -# 4. Geographic overlap (shared markets = tour collab opportunity) -curl -s "$RECOUP_API/research/audience?artist={ARTIST}&platform=instagram" -H "$AUTH" -curl -s "$RECOUP_API/research/audience?artist={collab_target}&platform=instagram" -H "$AUTH" - -# 5. Enrich collaborator with structured facts (label, management) if needed -curl -s -X POST "$RECOUP_API/research/enrich" -H "$AUTH" -H "Content-Type: application/json" \ - -d '{"input":"{collab_target} musician","schema":{"type":"object","properties":{"label":{"type":"string"},"manager":{"type":"string"}}}}' -``` - -**What to synthesize:** Ranked collaboration targets by audience overlap, size -(slightly bigger via `/metrics` = ideal exposure uplift), and playlist synergy. -Shared playlists + shared audience markets = strongest collab case. - ---- - -## 10. Release Strategy Timing - -**Question:** "When should we release, and how should we roll it out?" - -```bash -# 1. Analyze past releases (albums needs provider artist_id) -curl -s "$RECOUP_API/research?q={ARTIST}&type=artists" -H "$AUTH" # get id -curl -s "$RECOUP_API/research/albums?artist_id={artist_id}" -H "$AUTH" -curl -s "$RECOUP_API/research/career?artist={ARTIST}" -H "$AUTH" - -# 2. What worked — past playlist adds after previous releases -curl -s "$RECOUP_API/research/playlists?artist={ARTIST}&status=past" -H "$AUTH" - -# 3. Similar artists' release cadence -curl -s "$RECOUP_API/research/similar?artist={ARTIST}&audience=high&limit=10" -H "$AUTH" -# For each peer: -curl -s "$RECOUP_API/research?q={similar_artist}&type=artists" -H "$AUTH" # get peer id -curl -s "$RECOUP_API/research/albums?artist_id={peer_artist_id}" -H "$AUTH" -curl -s "$RECOUP_API/research/career?artist={similar_artist}" -H "$AUTH" - -# 4. Which platforms are hottest right now -curl -s "$RECOUP_API/research/metrics?artist={ARTIST}&source=spotify" -H "$AUTH" -curl -s "$RECOUP_API/research/metrics?artist={ARTIST}&source=tiktok" -H "$AUTH" -curl -s "$RECOUP_API/research/metrics?artist={ARTIST}&source=youtube_channel" -H "$AUTH" -``` - -**What to synthesize:** Release timing grounded in historical patterns (when did -past releases get the most playlist adds, from `career`/`milestones`?), peer -release cycles, and which platform has the most momentum right now. - ---- - -## 11. People Outreach (Managers, A&R, Press) - -**Question:** "Who should we reach out to?" - -```bash -# 1. People search — returns multi-source profiles (LinkedIn etc.) -curl -s -X POST "$RECOUP_API/research/people" -H "$AUTH" -H "Content-Type: application/json" \ - -d '{"query":"A&R reps at Atlantic Records hip-hop","num_results":15}' - -# 2. Pull source pages for specific candidates -curl -s -X POST "$RECOUP_API/research/extract" -H "$AUTH" -H "Content-Type: application/json" \ - -d '{"urls":["https://linkedin.com/in/...","https://example.com/team"],"objective":"role, tenure, recent signings"}' - -# 3. Enrich the candidate into structured form you can paste into a CRM -curl -s -X POST "$RECOUP_API/research/enrich" -H "$AUTH" -H "Content-Type: application/json" \ - -d '{"input":"Jane Doe A&R at Atlantic Records","schema":{"type":"object","properties":{"title":{"type":"string"},"recent_signings":{"type":"array","items":{"type":"string"}},"contact":{"type":"string"}}},"processor":"core"}' -``` - -**What to synthesize:** A ranked outreach list with title, recent relevant work, -and a suggested angle for the pitch. - ---- - -## Chaining workflows and tools - -The 11 workflows above are **building blocks, not complete answers**. Almost no -real user question maps cleanly onto exactly one workflow — the real deliverable -is usually a chain: `Workflow A` → `Workflow B` → external tool call → hand off -to another skill. - -This section teaches you how to compose. - -### The three chain patterns - -Every chain you'll ever build is one of three shapes: - -| Pattern | Shape | Example | -| ------- | ----- | ------- | -| **Data → Data** | Workflow A's output feeds Workflow B's input | `/similar` (W9) returns peer list → for each peer, run playlist gap analysis (W1) | -| **Data → Draft** | Research output becomes a deliverable the user can act on | Peer research (W9) + people search (W11) → drafted outreach email the user reviews and sends themselves | -| **Skill → Skill** | Finish with this skill, hand off to another | Research sweep (W4) → hand off to `content-creation` skill for press one-sheet; or to `release-management` for timing; or to `streaming-growth` for ads strategy | - -Most real deliverables are all three stacked: compose several workflows (Data → -Data), turn the result into a draft (Data → Draft), then hand off whatever -remains to another skill (Skill → Skill). - -### What this skill produces - -Beyond the `/research/*` endpoints, the agent running this skill typically produces: - -- **Written drafts** for the user to act on — outreach emails, pitch copy, press - blurbs, DSP pitches. This skill drafts; the user sends. It does not execute - external-facing actions (no email sending, no social posting, no contacting - managers). -- **Artist workspace writes** — save synthesized research to `context/artist.md`, - `research/{date}.md`, `releases/{slug}/RELEASE.md`. See "Saving research" above. -- **Handoffs to other skills** — `content-creation` (promo content, captions, - one-sheets), `release-management` (RELEASE.md lifecycle), `streaming-growth` - (Spotify Showcase/Marquee, DSP ads), `recoup-artist-workspace` (workspace - setup/lookup), `trend-to-song` (reverse from cultural moment to song). - -### Chaining rules (read before composing) - -1. **Don't re-fetch what you already have.** If `/metrics` already gave you the - peer's snapshot, reuse it — don't call it again. -2. **Preserve artist context through the chain.** Every downstream step should - reference the user's specific artist data (audience geography, playlist reach, - metrics), not generic templates. That's what makes the final output feel - researched instead of auto-generated. -3. **Draft, don't execute, for external-facing actions.** Anything that touches a - real human (cold outreach to managers/A&R, social posts, press contacts) - should be drafted and presented for the user to send themselves. -4. **Save once, reference many.** Write synthesized research to the artist - workspace once; subsequent chains read from there instead of re-running the - whole fan-out. Check `context/artist.md` before starting a new research pass. -5. **Stop when the question is answered.** Chains can loop forever if ungated. - Finish when the user's original ask is satisfied, not when you run out of - endpoints to call. - ---- - -### Example A — Peer collab outreach draft - -**User question:** "Who should Artist X collab with, and draft some outreach I can send?" - -```text -Step 1 — Research the artist (full sweep per SKILL.md decision tree) - Calls: /research/profile + /research/metrics?source=spotify - + /research/audience + /research/similar - Writes: context/artist.md (if workspace exists) - Output: full artist context — audience geography, metrics, peer set - -Step 2 — Narrow to realistic collab targets (Workflow 9) - Input: similar[] from Step 1 - Filters: size each peer via /metrics (slightly bigger = ideal), - audience overlap, shared-market overlap >= 2 - Output: 3-5 ranked peer candidates - -Step 3 — Find each target's manager / A&R (Workflow 11) - For each peer in Step 2: - POST /research/people {query: "manager for {peer}"} - POST /research/enrich {input, schema: {type: "object", properties: {name, role, email, recent_signings}}} - Output: ranked outreach list with contact + angle - -Step 4 — Draft outreach (LLM, no tool call) - Reference: - - Artist X specifics from Step 1 (markets, listeners, reach) - - Peer specifics from Step 3 (recent signings, stylistic fit) - Output: personalized drafts — NOT generic "love your music" copy. - Present drafts to the user with recipient, subject, and body. - The user sends them manually. - -Optional handoff: - → content-creation skill: draft the follow-up nurture sequence - → release-management skill: if a collab lands, open a RELEASE.md -``` - ---- - -### Example B — "How is Artist X doing, and what's next?" - -**User question:** State of the artist + recommended next move. - -```text -Step 1 — Snapshot - Calls: /research/profile + /research/metrics?source=spotify - + /research/audience + /research/insights + /research/milestones - Output: - - metrics.stats[0].data → monthly listeners, followers, popularity, - playlists_editorial_current, playlist_reach_current - - audience → geographic / demographic concentration - - milestones (sorted by activity_tier) → recent notable events - -Step 2 — Diagnose the gap (Workflow 1: playlist pitching) - Input: metrics snapshot from Step 1 - If playlists_editorial_current is low relative to monthly_listeners_current - → editorial gap confirmed - Call: /research/similar (peers) → /research/playlists (each peer) - Output: playlists peers are on that Artist X isn't → pitch targets - -Step 3 — HANDOFF to streaming-growth skill - Context to pass: - - Current monthly listeners + recent milestones from Step 1 - - Editorial gap from Step 2 - streaming-growth skill owns: which lever to pull first - (playlist push vs social-to-DSP ads vs organic content) - -Step 4 — HANDOFF to release-management skill - If Step 3 recommends a new release: - Open releases/{slug}/RELEASE.md - Populate DSP pitch from Step 1 data + Step 2 playlist targets - -Human-approval gate: none (internal reasoning only, no external contacts). -``` - ---- - -### Example C — Market scouting for a TikTok-driven breakout - -**User question:** "Find tracks that are blowing up on TikTok and the indie artists driving them." - -```text -Step 1 — Find candidates (no charts/discover endpoints — start from web + anchors) - Calls: POST /research/web {query: "songs blowing up on TikTok {GENRE} 2026"} - /research?q={ANCHOR_ARTIST}&type=artists - /research/similar?artist={ANCHOR}&musicality=high&genre=high - Output: candidate track + artist list - -Step 2 — Validate TikTok-to-Spotify pipeline (Workflow 2) - For each candidate: - /research/metrics?source=tiktok (TikTok scale) - /research/metrics?source=spotify (listener response) - /research/track/playlists?id={track_id} (editorial pickup?) - Output: ranked by TikTok scale + Spotify conversion + editorial pickup - -Step 3 — Enrich (optional, deep context) - POST /research/deep {query: "why is {track} going viral?"} - Output: cited narrative — the cultural thread driving the trend - -Step 4 — Write to artist workspace - If any candidate warrants ongoing tracking: - Create context/artist.md scaffold via recoup-artist-workspace skill - Save Step 1-3 synthesis to research/{date}-tiktok-scout.md - -Step 5 — HANDOFF to trend-to-song skill - If the trend itself (not the artist) is the opportunity: - trend-to-song owns: reverse from cultural moment to song - + test campaign in 72 hours - -Human-approval gate: none for research; required if Step 5 triggers an ad spend. -``` - ---- - -### When NOT to chain - -Chaining has a cost — every step is latency, credits, and a place for the agent -to lose context. Don't chain when: - -- The user asked a single-fact question ("how many Spotify followers does Artist - X have?"). Just answer it. -- The next workflow depends on data the previous one already surfaced well - enough. Reuse the data; don't re-query. -- You're about to write to the artist workspace or kick off a long chain without - clear user intent. Confirm first. - -A three-step chain that answers the real question beats an eleven-step chain that -shows off every endpoint. - ---- - -## Building Your Own Workflows - -The power is in combining data types: - -| What You Need | Endpoint | Use For | -|---------------|----------|---------| -| **Who** — peers, competitors, collaborators | `/research/similar` | Benchmarks, pitch targets, collabs | -| **Where** — geography | `/research/audience` (country breakdown) | Tour routing, market expansion | -| **What** — content and catalog | `/research/playlists`, `/research/tracks`, `/research/albums`, `/research/track`, `/research/track/playlists` | Content strategy, playlist pitching | -| **How big / how hot** — metrics | `/research/metrics` (snapshot per source) | Sizing artists, release timing, growth analysis | -| **When** — timeline & events | `/research/career`, `/research/milestones` | Release timing, activity feeds | -| **Why** — narrative + context | `/research/insights`, `/research/web`, `/research/deep` | Cultural positioning, press strategy, brand partnerships | -| **People** — industry contacts | `/research/people`, `/research/enrich`, `/research/extract` | Outreach, CRM enrichment | - -Endpoints that no longer exist (don't reach for them): `cities`, `charts`, -`discover`, `genres`, `festivals`, `radio`, `venues`, `rank`, `instagram-posts`, -`playlist` (singular), `curator`. Geography → `/research/audience`; discovery → -anchor artist + `/research/similar` + `/research/web`; headline rank → compare -`/research/metrics` snapshots. diff --git a/skills/recoup-account-status/SKILL.md b/skills/recoup-account-status/SKILL.md deleted file mode 100644 index e0c7d7b..0000000 --- a/skills/recoup-account-status/SKILL.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -name: recoup-account-status -description: >- - Produce an account-status / account-health snapshot for a Recoup account from - an email, account ID, or artist name. Resolves the account, pulls its artists, - socials, chats, scheduled tasks, sandboxes, credits and subscription, then - generates an ACCOUNT.md, a per-artist ARTIST.md tree, and a polished status - PDF. Use when asked to "check this account", "account status", "account - health", "how are they using the product", "what artists / chats does this - account have", "evaluate this user/profile", or to find the sign-in email - behind an artist. Requires a Recoup API token (an admin token can inspect any - account; an owner token only its own). ---- - -# Recoup Account Status - -Builds a complete picture of how a Recoup account uses the product and packages -it as Markdown + a PDF. This is the workflow that produced the Sound of Fractures -account status report. - -## When to use - -- "Summarize / evaluate the status of this account or profile." -- "Do they have artists? Are they opening chats? How are they using the product?" -- "Find the email an artist used to sign in" (admin token). -- "Build an account-status PDF / ACCOUNT.md / per-artist folders." - -## Prerequisites - -- A Recoup API bearer token in `RECOUP_ACCESS_TOKEN` (or pass `--token`). - - An **admin** token can read any account via the `account_id` override on - list endpoints. Confirm admin with `GET /api/admins` → `{"isAdmin": true}`. - - An **owner** token only sees its own account. -- `reportlab` for the PDF: `pip install reportlab --break-system-packages`. -- Base URL: `https://api.recoupable.com/api` (override with `RECOUP_API_BASE`). - Load the `recoup-api` skill for endpoint details. - -## Quick run - -```bash -export RECOUP_ACCESS_TOKEN="" -python3 scripts/generate_account_status.py \ - --email soundoffractures@gmail.com \ - --out-dir / -# or: --account-id -``` - -Outputs into `--out-dir`: - -- `ACCOUNT.md` — identity, plan & usage, verdict, artist roster table. -- `artists/INDEX.md` — one-line roster across all records. -- `artists/-/ARTIST.md` — per artist: socials (handle, followers, - last-scraped date), chats, and tasks. The short ID suffix keeps duplicate - same-name records distinct. -- `-Account-Status.pdf` — executive snapshot. Page 1 is the identity / - usage / verdict / roster; page 2 is the **Most relevant chats** table. - -Both `ACCOUNT.md` and the PDF include a **Most relevant chats** table — the top 10 -chats across all of the account's artists, ranked to show what the account is -actually working on in Recoup (see step 4 below). - -## Workflow (what the script automates) - -1. **Resolve the account.** - - By email: `POST /api/accounts {"email": "..."}` returns the existing account - (UUID, name, emails, wallets) — it does not create one if it already exists. - - By id: `GET /api/accounts/{id}`. - - To find an account behind an artist name from an admin token, search Privy - logins: `GET /api/admins/privy?period=all` and grep the linked email. -2. **Pull account-level data** (pass `?account_id=` as admin): - - `GET /api/accounts/{id}/subscription` — isPro, plan, status. - - `GET /api/accounts/{id}/credits` — used vs. total. - - `GET /api/organizations?account_id=`. - - `GET /api/sandboxes?account_id=`. - - `GET /api/artists?account_id=` — the roster. -3. **Per artist:** - - `GET /api/artists/{artistId}/socials` — connected platforms + followers. - - `GET /api/chats?artist_account_id={artistId}&account_id=`. - - `GET /api/tasks?artist_account_id={artistId}&account_id=`. -4. **Synthesize a verdict.** If chats, tasks, sandboxes and credits-used are all - zero, the account is **dormant**; otherwise it shows real engagement. Flag - data hygiene issues (duplicate artist records, mismatched social auto-matches, - missing platforms). -5. **Rank the chats** (`rank_chats`). Collect every chat across all artists and - score each by a topic signal (regex keyword buckets over the chat **title** — - `CHAT_THEMES`) plus a recency bonus, then take the top 10. Each row shows the - title, last-active date, and inferred theme (e.g. *Strategy & frameworks*, - *Analysis & metrics*, *Content production*). For multi-artist accounts an - **Artist** column is added automatically. Titles only: see the chat-bodies - caveat below. -6. **Render** `ACCOUNT.md`, the `artists/` tree, and the PDF, then present the PDF - to the user. - -## PDF styling notes (don't regress these) - -- **White header text on dark rows.** A reportlab `Paragraph` carries its own - `textColor`, so a `TableStyle` `TEXTCOLOR` on the header row is **not** enough — - the text renders black on navy and is unreadable. Every navy header row must use - the dedicated `CELLBW` (white bold) paragraph style for its cells. This applies - to the Product-engagement, Artists, and Most-relevant-chats tables. The identity - table and the left "Metric" label column sit on a light background and correctly - use the dark `CELLB` style. -- The chats table lives on its own page (`PageBreak`) and repeats its header row - (`repeatRows=1`) if it ever spills. - -## Notes & caveats - -- Pre-migration chat sessions may not surface in current endpoints — cross-check - multiple usage signals before declaring an account dead. -- The `account_id` override is honored for **reads** with an admin token but - **not** for `DELETE /api/artists/{id}` (that route only unlinks the caller's - own artist link). Deleting another account's records needs an owner-scoped - token or an in-app/admin action. -- Social auto-matches can be wrong (e.g. a Spotify match returning an unrelated - major artist). Treat large follower counts on a small artist as suspect. -- **Chat bodies are owner-scoped.** The admin `account_id` override works for the - chat **list** (`GET /api/chats`), so titles, dates and counts are available. But - per-chat message bodies are not: `GET /api/chats/{id}/messages` returns - `Chat room not found` and `GET /api/sessions/{sessionId}/chats/{chatId}` returns - `Forbidden` for another account's chats. The Most-relevant-chats ranking and - themes are therefore derived from the **auto-generated title** (which Recoup - builds from the chat's opening prompt), not the transcript. To produce - transcript-level summaries, run with an **owner-scoped** token for that account. -- Chat titles can be long; the script truncates display titles (~70–78 chars). diff --git a/skills/recoup-account-status/scripts/generate_account_status.py b/skills/recoup-account-status/scripts/generate_account_status.py deleted file mode 100644 index e6deabe..0000000 --- a/skills/recoup-account-status/scripts/generate_account_status.py +++ /dev/null @@ -1,464 +0,0 @@ -#!/usr/bin/env python3 -"""Generate an account-status package for a Recoup account. - -Resolves an account (by email or account id), pulls its artists, socials, -chats, tasks, sandboxes, credits and subscription, then writes: - - /ACCOUNT.md - /artists/INDEX.md - /artists/-/ARTIST.md (one per artist) - /-Account-Status.pdf - -Auth: pass --token or set RECOUP_ACCESS_TOKEN. An admin token can read any -account via the account_id override on list endpoints. - -Usage: - python3 generate_account_status.py --email artist@example.com --out-dir ./artist-folder - python3 generate_account_status.py --account-id --out-dir ./artist-folder -""" -import argparse, datetime, json, os, re, sys, urllib.request, urllib.parse - -BASE = os.environ.get("RECOUP_API_BASE", "https://api.recoupable.com") # endpoint paths already include the /api prefix - - -def api(method, path, token, body=None): - url = BASE + path - data = json.dumps(body).encode() if body is not None else None - req = urllib.request.Request(url, data=data, method=method, - headers={"Authorization": "Bearer " + token, - "Content-Type": "application/json"}) - try: - with urllib.request.urlopen(req, timeout=60) as r: - return json.loads(r.read().decode()), r.status - except urllib.error.HTTPError as e: - try: - return json.loads(e.read().decode()), e.code - except Exception: - return {"_error": str(e)}, e.code - except Exception as e: - return {"_error": str(e)}, 0 - - -def slug(s): - s = re.sub(r"[^a-z0-9]+", "-", (s or "").strip().lower()).strip("-") - return s or "untitled" - - -def fmt_ts(v): - if not v: - return "—" - try: - if isinstance(v, (int, float)): - return datetime.datetime.utcfromtimestamp(v / 1000 if v > 1e12 else v).strftime("%Y-%m-%d") - return str(v)[:10] - except Exception: - return str(v) - - -def platform_of(url): - if not url: - return "—" - m = re.match(r"(?:https?://)?(?:www\.)?([^/]+)", url) - return m.group(1) if m else url - - -def to_epoch(v): - """Best-effort parse of an updatedAt/timestamp value to epoch seconds.""" - if not v: - return 0 - if isinstance(v, (int, float)): - return v / 1000 if v > 1e12 else v - s = str(v).strip().replace("Z", "+00:00") - for fmt in ("%Y-%m-%dT%H:%M:%S.%f%z", "%Y-%m-%dT%H:%M:%S%z", - "%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d"): - try: - return datetime.datetime.strptime(s[:32] if "." in s else s, fmt).timestamp() - except Exception: - continue - try: - return datetime.datetime.fromisoformat(s).timestamp() - except Exception: - return 0 - - -# Theme buckets used to score/label chats from their auto-generated titles. -# (label, regex, weight) — highest-weight matched theme becomes the row's label. -CHAT_THEMES = [ - ("Strategy & frameworks", r"framework|strateg|playbook|roadmap|\bplan\b|positioning", 5), - ("Analysis & metrics", r"analysis|audit|competitive|benchmark|metrics|\breport\b|rss", 4), - ("Brand & growth", r"brand|proposal|growth|viral|selection|audience|outreach", 3), - ("Research & prep", r"guide|insight|overview|scenario|\bbio\b|interview|question|research|prep", 2), - ("Content production", r"thumbnail|image|description|caption|episode|content|photo|video|\bep\.", 1), -] - - -def rank_chats(chats, top_n=10): - """Score chats by topic signal (from the title) + recency, return the top N. - - Full transcripts are not retrievable with an admin token (per-chat message - bodies are owner-scoped), so ranking uses the auto-generated title plus how - recently the chat was active. Each chat gets a `_theme`, `_date`, `_ts`. - """ - for c in chats: - c["_ts"] = to_epoch(c.get("updatedAt") or c.get("updated_at") or c.get("timestamp")) - c["_date"] = fmt_ts(c.get("updatedAt") or c.get("updated_at") or c.get("timestamp")) - ts_vals = [c["_ts"] for c in chats if c["_ts"]] - newest, oldest = (max(ts_vals), min(ts_vals)) if ts_vals else (0, 0) - span = (newest - oldest) or 1 - for c in chats: - title = (c.get("title") or "").lower() - score, theme, best_w = 0.0, "General", -1 - for label, pat, w in CHAT_THEMES: - if re.search(pat, title): - score += w - if w > best_w: - best_w, theme = w, label - if c["_ts"]: - score += 3.0 * (c["_ts"] - oldest) / span # recency bonus 0..3 - c["_score"], c["_theme"] = score, theme - return sorted(chats, key=lambda c: (c["_score"], c["_ts"]), reverse=True)[:top_n] - - -def gather(token, email=None, account_id=None): - # Resolve account - if email and not account_id: - acct, _ = api("POST", "/api/accounts", token, {"email": email}) - data = acct.get("data", acct) - account_id = data.get("account_id") or data.get("id") - else: - data, _ = api("GET", f"/api/accounts/{account_id}", token) - data = data.get("data", data) - if not account_id: - raise SystemExit("Could not resolve account. Check the email/id and token.") - - A = account_id - name = (data.get("name") or "").strip() or "(unnamed)" - emails = [e.get("email") for e in (data.get("account_emails") or []) if e.get("email")] - primary_email = email or (emails[0] if emails else "—") - wallets = [w.get("address") for w in (data.get("account_wallets") or []) if w.get("address")] - - sub, _ = api("GET", f"/api/accounts/{A}/subscription", token) - credits, _ = api("GET", f"/api/accounts/{A}/credits", token) - orgs, _ = api("GET", f"/api/organizations?account_id={A}", token) - sandboxes, _ = api("GET", f"/api/sandboxes?account_id={A}", token) - artists_resp, _ = api("GET", f"/api/artists?account_id={A}", token) - artists = artists_resp.get("artists", []) or [] - - enriched = [] - all_chats = [] - total_chats = total_tasks = 0 - for a in artists: - aid = a.get("account_id") or a.get("id") - aname = (a.get("name") or "").strip() - socials = (api("GET", f"/api/artists/{aid}/socials", token)[0].get("socials") or []) - chats_resp = api("GET", f"/api/chats?artist_account_id={aid}&account_id={A}", token)[0] - chats = chats_resp.get("chats", chats_resp if isinstance(chats_resp, list) else []) or [] - tasks = (api("GET", f"/api/tasks?artist_account_id={aid}&account_id={A}", token)[0].get("tasks") or []) - nchat = len(chats) if isinstance(chats, list) else 0 - total_chats += nchat - total_tasks += len(tasks) - if isinstance(chats, list): - for c in chats: - if isinstance(c, dict): - c.setdefault("_artist", aname) - all_chats.append(c) - enriched.append({"id": aid, "name": aname, - "socials": socials, "chats": nchat, "tasks": len(tasks)}) - - # Rank chats across all artists by topic signal + recency. Transcript bodies - # are owner-scoped, so this works from each chat's auto-generated title. - top_chats = rank_chats(all_chats, top_n=10) - - return { - "account_id": A, "name": name, "email": primary_email, "emails": emails, - "wallets": wallets, - "subscription": sub, "credits": credits, - "orgs": orgs.get("organizations", []) or [], - "sandboxes": (sandboxes.get("sandboxes") or []) if isinstance(sandboxes, dict) else [], - "artists": enriched, "total_chats": total_chats, "total_tasks": total_tasks, - "top_chats": top_chats, "multi_artist": len(artists) > 1, - } - - -def write_markdown(ctx, out_dir, today): - A = ctx["account_id"] - arts_dir = os.path.join(out_dir, "artists") - os.makedirs(arts_dir, exist_ok=True) - creds = ctx["credits"] if isinstance(ctx["credits"], dict) else {} - sub = ctx["subscription"] if isinstance(ctx["subscription"], dict) else {} - used = creds.get("used_credits", 0) - total = creds.get("total_credits", 0) - is_pro = sub.get("isPro", False) - n_sandbox = len(ctx["sandboxes"]) - dormant = (ctx["total_chats"] == 0 and ctx["total_tasks"] == 0 and n_sandbox == 0 and (used or 0) == 0) - verdict = ("**Dormant — minimal or no product engagement.** No chats, tasks, sandbox runs, or credit " - "usage on record." if dormant else - "**Active — the account shows real product engagement.**") - - # ACCOUNT.md - rows = [] - for art in ctx["artists"]: - disp = art["name"] or "(untitled)" - folder = f"{slug(art['name'])}-{art['id'].split('-')[0]}" - rows.append(f"| {disp} | `{folder}` | {len(art['socials'])} | {art['chats']} | {art['tasks']} |") - - # Most relevant chats — top 10 by topic signal + recency (from titles). - top_chats = ctx.get("top_chats") or [] - if top_chats: - show_artist = ctx.get("multi_artist") - head = "| # | Chat | Last active | Theme |" + (" Artist |" if show_artist else "") - sep = "|---|---|---|---|" + ("---|" if show_artist else "") - crows = [head, sep] - for i, c in enumerate(top_chats, 1): - title = (c.get("title") or "(untitled)").strip().replace("|", "/") - if len(title) > 70: - title = title[:69].rstrip() + "…" - extra = f" {c.get('_artist') or '—'} |" if show_artist else "" - crows.append(f"| {i} | {title} | {c.get('_date', '—')} | {c.get('_theme', 'General')} |{extra}") - chats_section = f"""## Most relevant chats (top {len(top_chats)} of {ctx['total_chats']}) - -Ranked by topic signal and recency from each chat's auto-generated title. The -theme is inferred from the title; full transcripts are not retrievable with an -admin token (see note below). - -{chr(10).join(crows)} - -""" - else: - chats_section = "" - - acct_md = f"""# Account — {ctx['name']} - -_Snapshot generated {today} from the Recoup API._ - -## Identity - -| Field | Value | -|---|---| -| Account name | {ctx['name']} | -| Sign-in email | {ctx['email']} | -| Account ID | `{A}` | -| Wallet(s) | {', '.join(ctx['wallets']) or '—'} | - -## Plan & usage - -| Field | Value | -|---|---| -| Subscription | {sub.get('plan') or sub.get('status') or 'None (free)'} | -| Pro status | {'Yes' if is_pro else 'No'} | -| Credits | {used or 0} used of {total or 0} granted | -| Organizations | {len(ctx['orgs'])} | -| Chats opened | {ctx['total_chats']} | -| Scheduled tasks | {ctx['total_tasks']} | -| Sandboxes | {n_sandbox} | - -## Verdict - -{verdict} - -## Artists ({len(ctx['artists'])} records) - -| Artist | Folder | Socials | Chats | Tasks | -|---|---|---|---|---| -{chr(10).join(rows) if rows else '| _none_ | | | | |'} - -{chats_section}> Notes: -> - Pre-migration sessions may not surface in current endpoints; cross-check usage signals. -> - Chat summaries are derived from each chat's auto-generated title. An admin token's -> `account_id` override is honored for list reads (chats, artists, tasks) but per-chat -> message bodies are owner-scoped and return `Forbidden`/`Chat room not found`. Use an -> owner-scoped token for the account to produce transcript-level summaries. -""" - with open(os.path.join(out_dir, "ACCOUNT.md"), "w") as f: - f.write(acct_md) - - # Per-artist ARTIST.md + index - idx = ["# Artists index\n", f"_{len(ctx['artists'])} records under account `{A}` ({ctx['name']} / {ctx['email']})._\n", - "| Artist | Folder | Socials | Chats | Tasks |", "|---|---|---|---|---|"] - for art in ctx["artists"]: - disp = art["name"] or "(untitled)" - folder = f"{slug(art['name'])}-{art['id'].split('-')[0]}" - fdir = os.path.join(arts_dir, folder) - os.makedirs(fdir, exist_ok=True) - if art["socials"]: - srows = ["| Platform | Handle | Followers | Last updated |", "|---|---|---|---|"] - for s in art["socials"]: - foll = s.get("followerCount") or s.get("follower_count") or s.get("followers") - foll = f"{foll:,}" if isinstance(foll, int) else "—" - srows.append(f"| {platform_of(s.get('profile_url'))} | {s.get('username') or '—'} | {foll} | {fmt_ts(s.get('updated_at'))} |") - socials_md = "\n".join(srows) - else: - socials_md = "_No socials connected._" - art_md = f"""# {disp} - -**Artist account ID:** `{art['id']}` -**Owning account:** {ctx['name']} · {ctx['email']} (`{A}`) -**Socials connected:** {len(art['socials'])} -**Chats:** {art['chats']} · **Scheduled tasks:** {art['tasks']} - -_Snapshot generated {today} from the Recoup API._ - -## Socials connected - -{socials_md} - -## Chats - -{('_No chats opened._' if art['chats'] == 0 else f"{art['chats']} chat(s) on record.")} - -## Scheduled tasks - -{('_No scheduled tasks._' if art['tasks'] == 0 else f"{art['tasks']} task(s) on record.")} - -## Notes - -- _Add context, brand, and next steps here as we build this artist's plan._ -""" - with open(os.path.join(fdir, "ARTIST.md"), "w") as f: - f.write(art_md) - idx.append(f"| {disp} | `{folder}` | {len(art['socials'])} | {art['chats']} | {art['tasks']} |") - with open(os.path.join(arts_dir, "INDEX.md"), "w") as f: - f.write("\n".join(idx) + "\n") - return dormant - - -def build_pdf(ctx, out_dir, today, dormant): - try: - from reportlab.lib.pagesizes import letter - from reportlab.lib.units import inch - from reportlab.lib import colors - from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table, - TableStyle, HRFlowable, PageBreak) - from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle - from reportlab.lib.enums import TA_LEFT - except ImportError: - print("reportlab not installed — skipping PDF (pip install reportlab --break-system-packages)") - return None - - NAVY, ACCENT, GREY, LIGHT = (colors.HexColor(c) for c in ("#1a2b4a", "#2f6df0", "#5b6472", "#eef1f6")) - st = getSampleStyleSheet() - H1 = ParagraphStyle("H1", parent=st["Title"], textColor=NAVY, fontSize=22, alignment=TA_LEFT, spaceAfter=2) - SUB = ParagraphStyle("SUB", parent=st["Normal"], textColor=GREY, fontSize=10) - H2 = ParagraphStyle("H2", parent=st["Heading2"], textColor=NAVY, fontSize=13, spaceBefore=14, spaceAfter=6) - BODY = ParagraphStyle("BODY", parent=st["Normal"], fontSize=9.5, leading=14) - CELL = ParagraphStyle("CELL", parent=st["Normal"], fontSize=8.5, leading=11) - CELLB = ParagraphStyle("CELLB", parent=CELL, fontName="Helvetica-Bold") - # White bold cell for dark navy header rows. A Paragraph carries its own text - # color, so a TableStyle TEXTCOLOR alone won't whiten header text — use this. - CELLBW = ParagraphStyle("CELLBW", parent=CELLB, textColor=colors.white) - NOTE = ParagraphStyle("NOTE", parent=st["Normal"], fontSize=8, leading=11, textColor=GREY) - creds = ctx["credits"] if isinstance(ctx["credits"], dict) else {} - sub = ctx["subscription"] if isinstance(ctx["subscription"], dict) else {} - - story = [Paragraph(f"Account Status — {ctx['name']}", H1), - Paragraph(f"Recoup account health snapshot  |  Prepared {today}", SUB), - HRFlowable(width="100%", thickness=2, color=ACCENT, spaceBefore=6, spaceAfter=10)] - ident = [["Account name", ctx["name"], "Sign-in email", ctx["email"]], - ["Account ID", ctx["account_id"], "Organizations", str(len(ctx["orgs"]))]] - t = Table([[Paragraph(c, CELLB if i % 2 == 0 else CELL) for i, c in enumerate(r)] for r in ident], - colWidths=[1.1*inch, 2.2*inch, 1.1*inch, 2.1*inch]) - t.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), LIGHT), ("BOX", (0, 0), (-1, -1), 0.5, colors.HexColor("#d4dae6")), - ("INNERGRID", (0, 0), (-1, -1), 0.5, colors.white), ("TOPPADDING", (0, 0), (-1, -1), 5), - ("BOTTOMPADDING", (0, 0), (-1, -1), 5), ("LEFTPADDING", (0, 0), (-1, -1), 7)])) - story += [t, Spacer(1, 12)] - vtxt = ("Dormant — minimal or no product engagement." if dormant else "Active — real product engagement.") - v = Table([[Paragraph(f"Verdict: {vtxt}", ParagraphStyle("v", parent=BODY, textColor=colors.white, fontSize=10.5))]], - colWidths=[6.5*inch]) - v.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), NAVY), ("TOPPADDING", (0, 0), (-1, -1), 8), - ("BOTTOMPADDING", (0, 0), (-1, -1), 8), ("LEFTPADDING", (0, 0), (-1, -1), 10)])) - story.append(v) - - story.append(Paragraph("Product engagement", H2)) - eng = [["Metric", "Value", "Metric", "Value"], - ["Chats opened", str(ctx["total_chats"]), "Scheduled tasks", str(ctx["total_tasks"])], - ["Sandboxes", str(len(ctx["sandboxes"])), "Credits used", f"{creds.get('used_credits', 0)} of {creds.get('total_credits', 0)}"], - ["Subscription", (sub.get("plan") or sub.get("status") or "None"), "Pro", "Yes" if sub.get("isPro") else "No"]] - te = Table([[Paragraph(c, CELLBW if ri == 0 else (CELLB if ci % 2 == 0 else CELL)) for ci, c in enumerate(r)] for ri, r in enumerate(eng)], - colWidths=[1.65*inch, 1.7*inch, 1.65*inch, 1.5*inch]) - te.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, 0), NAVY), ("TEXTCOLOR", (0, 0), (-1, 0), colors.white), - ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, LIGHT]), - ("BOX", (0, 0), (-1, -1), 0.5, colors.HexColor("#d4dae6")), - ("INNERGRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#d4dae6")), - ("TOPPADDING", (0, 0), (-1, -1), 5), ("BOTTOMPADDING", (0, 0), (-1, -1), 5), ("LEFTPADDING", (0, 0), (-1, -1), 7)])) - story.append(te) - - story.append(Paragraph(f"Artists ({len(ctx['artists'])} records)", H2)) - arows = [["Artist", "Socials", "Chats", "Tasks"]] - for a in ctx["artists"]: - arows.append([a["name"] or "(untitled)", str(len(a["socials"])), str(a["chats"]), str(a["tasks"])]) - ta = Table([[Paragraph(c, CELLBW if ri == 0 else CELL) for c in r] for ri, r in enumerate(arows)], - colWidths=[3.0*inch, 1.4*inch, 1.05*inch, 1.05*inch]) - ta.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, 0), NAVY), ("TEXTCOLOR", (0, 0), (-1, 0), colors.white), - ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, LIGHT]), - ("BOX", (0, 0), (-1, -1), 0.5, colors.HexColor("#d4dae6")), - ("INNERGRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#d4dae6")), - ("TOPPADDING", (0, 0), (-1, -1), 5), ("BOTTOMPADDING", (0, 0), (-1, -1), 5), ("LEFTPADDING", (0, 0), (-1, -1), 7)])) - story.append(ta) - - # Most relevant chats — new page, white headers. - top_chats = ctx.get("top_chats") or [] - if top_chats: - show_artist = ctx.get("multi_artist") - story.append(PageBreak()) - story.append(Paragraph(f"Most relevant chats (top {len(top_chats)} of {ctx['total_chats']})", H2)) - story.append(Paragraph( - "Ranked by topic signal and recency from each chat's auto-generated title. The theme is " - "inferred from the title; full transcripts are not retrievable with an admin token.", BODY)) - story.append(Spacer(1, 8)) - header = ["#", "Chat", "Last active", "Theme"] + (["Artist"] if show_artist else []) - crows = [header] - for i, c in enumerate(top_chats, 1): - title = (c.get("title") or "(untitled)").strip() - if len(title) > 78: - title = title[:77].rstrip() + "…" - row = [str(i), title, c.get("_date", "—"), c.get("_theme", "General")] - if show_artist: - row.append(c.get("_artist") or "—") - crows.append(row) - widths = ([0.3*inch, 3.05*inch, 0.95*inch, 1.55*inch, 1.05*inch] if show_artist - else [0.3*inch, 3.7*inch, 1.0*inch, 1.9*inch]) - tc = Table([[Paragraph(c, CELLBW if ri == 0 else CELL) for c in r] for ri, r in enumerate(crows)], - colWidths=widths, repeatRows=1) - tc.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, 0), NAVY), ("TEXTCOLOR", (0, 0), (-1, 0), colors.white), - ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, LIGHT]), - ("BOX", (0, 0), (-1, -1), 0.5, colors.HexColor("#d4dae6")), - ("INNERGRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#d4dae6")), - ("VALIGN", (0, 0), (-1, -1), "TOP"), - ("TOPPADDING", (0, 0), (-1, -1), 5), ("BOTTOMPADDING", (0, 0), (-1, -1), 5), - ("LEFTPADDING", (0, 0), (-1, -1), 6)])) - story.append(tc) - story.append(Spacer(1, 10)) - story.append(Paragraph( - "Data note: Summaries are derived from each chat's auto-generated title (created from its " - "opening prompt). The admin token's account_id override is honored for list reads but per-chat " - "message bodies are owner-scoped and return Forbidden. An owner-scoped token for the account " - "would allow transcript-level summaries.", NOTE)) - - out = os.path.join(out_dir, f"{slug(ctx['name'])}-Account-Status.pdf") - SimpleDocTemplate(out, pagesize=letter, topMargin=0.7*inch, bottomMargin=0.6*inch, - leftMargin=0.75*inch, rightMargin=0.75*inch, title=f"{ctx['name']} - Account Status").build(story) - return out - - -def main(): - p = argparse.ArgumentParser(description="Generate a Recoup account-status package.") - g = p.add_mutually_exclusive_group(required=True) - g.add_argument("--email") - g.add_argument("--account-id") - p.add_argument("--out-dir", required=True) - p.add_argument("--token", default=os.environ.get("RECOUP_ACCESS_TOKEN", "")) - args = p.parse_args() - if not args.token: - sys.exit("No token. Pass --token or set RECOUP_ACCESS_TOKEN.") - today = datetime.date.today().strftime("%B %-d, %Y") - os.makedirs(args.out_dir, exist_ok=True) - ctx = gather(args.token, email=args.email, account_id=args.account_id) - dormant = write_markdown(ctx, args.out_dir, today) - pdf = build_pdf(ctx, args.out_dir, today, dormant) - print(f"Account: {ctx['name']} ({ctx['account_id']})") - print(f"Artists: {len(ctx['artists'])} | chats={ctx['total_chats']} tasks={ctx['total_tasks']}") - print(f"Wrote ACCOUNT.md, artists/ tree" + (f", and {os.path.basename(pdf)}" if pdf else "")) - - -if __name__ == "__main__": - main() diff --git a/plugins/recoup-records/skills/recoup-catalog-estimate-value/SKILL.md b/skills/recoup-catalog-estimate-value/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-estimate-value/SKILL.md rename to skills/recoup-catalog-estimate-value/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-catalog-estimate-value/references/methodology.md b/skills/recoup-catalog-estimate-value/references/methodology.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-estimate-value/references/methodology.md rename to skills/recoup-catalog-estimate-value/references/methodology.md diff --git a/plugins/recoup-records/skills/recoup-catalog-estimate-value/references/recoup-api.md b/skills/recoup-catalog-estimate-value/references/recoup-api.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-estimate-value/references/recoup-api.md rename to skills/recoup-catalog-estimate-value/references/recoup-api.md diff --git a/plugins/recoup-records/skills/recoup-catalog-estimate-value/scripts/build_report.py b/skills/recoup-catalog-estimate-value/scripts/build_report.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-estimate-value/scripts/build_report.py rename to skills/recoup-catalog-estimate-value/scripts/build_report.py diff --git a/plugins/recoup-records/skills/recoup-catalog-estimate-value/scripts/estimate.py b/skills/recoup-catalog-estimate-value/scripts/estimate.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-estimate-value/scripts/estimate.py rename to skills/recoup-catalog-estimate-value/scripts/estimate.py diff --git a/plugins/recoup-records/skills/recoup-catalog-estimate-value/scripts/fetch_album_tracks.py b/skills/recoup-catalog-estimate-value/scripts/fetch_album_tracks.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-estimate-value/scripts/fetch_album_tracks.py rename to skills/recoup-catalog-estimate-value/scripts/fetch_album_tracks.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/SKILL.md b/skills/recoup-catalog-review-deal/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/SKILL.md rename to skills/recoup-catalog-review-deal/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/00_overview/README.md b/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/00_overview/README.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/00_overview/README.md rename to skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/00_overview/README.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/01_track_list/track-list.csv b/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/01_track_list/track-list.csv similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/01_track_list/track-list.csv rename to skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/01_track_list/track-list.csv diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/02_compositions/composition-list.csv b/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/02_compositions/composition-list.csv similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/02_compositions/composition-list.csv rename to skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/02_compositions/composition-list.csv diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/ascap/ascap-statements-2024.csv b/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/ascap/ascap-statements-2024.csv similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/ascap/ascap-statements-2024.csv rename to skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/ascap/ascap-statements-2024.csv diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/distributor/distributor-2024.csv b/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/distributor/distributor-2024.csv similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/distributor/distributor-2024.csv rename to skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/distributor/distributor-2024.csv diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/mlc/mlc-2024.csv b/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/mlc/mlc-2024.csv similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/mlc/mlc-2024.csv rename to skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/mlc/mlc-2024.csv diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/soundexchange/soundexchange-2024.csv b/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/soundexchange/soundexchange-2024.csv similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/soundexchange/soundexchange-2024.csv rename to skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/soundexchange/soundexchange-2024.csv diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/youtube/youtube-cid-2024.csv b/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/youtube/youtube-cid-2024.csv similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/youtube/youtube-cid-2024.csv rename to skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/03_royalty_statements/youtube/youtube-cid-2024.csv diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/04_rights_documents/agreements/publishing-admin-agreement-2019.txt b/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/04_rights_documents/agreements/publishing-admin-agreement-2019.txt similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/04_rights_documents/agreements/publishing-admin-agreement-2019.txt rename to skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/04_rights_documents/agreements/publishing-admin-agreement-2019.txt diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/04_rights_documents/agreements/recording-agreement-2018.txt b/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/04_rights_documents/agreements/recording-agreement-2018.txt similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/04_rights_documents/agreements/recording-agreement-2018.txt rename to skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/04_rights_documents/agreements/recording-agreement-2018.txt diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/04_rights_documents/split_sheets/bright-lights-split.txt b/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/04_rights_documents/split_sheets/bright-lights-split.txt similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/04_rights_documents/split_sheets/bright-lights-split.txt rename to skills/recoup-catalog-review-deal/fixtures/demo-data-room/source/04_rights_documents/split_sheets/bright-lights-split.txt diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/canonical-schema.md b/skills/recoup-catalog-review-deal/references/canonical-schema.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/canonical-schema.md rename to skills/recoup-catalog-review-deal/references/canonical-schema.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/cleaning-rules.md b/skills/recoup-catalog-review-deal/references/cleaning-rules.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/cleaning-rules.md rename to skills/recoup-catalog-review-deal/references/cleaning-rules.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/data-room-checklist.md b/skills/recoup-catalog-review-deal/references/data-room-checklist.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/data-room-checklist.md rename to skills/recoup-catalog-review-deal/references/data-room-checklist.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/deal-workflow.md b/skills/recoup-catalog-review-deal/references/deal-workflow.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/deal-workflow.md rename to skills/recoup-catalog-review-deal/references/deal-workflow.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/deal-workspace.md b/skills/recoup-catalog-review-deal/references/deal-workspace.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/deal-workspace.md rename to skills/recoup-catalog-review-deal/references/deal-workspace.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/financing-underwrite.md b/skills/recoup-catalog-review-deal/references/financing-underwrite.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/financing-underwrite.md rename to skills/recoup-catalog-review-deal/references/financing-underwrite.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/methodology.md b/skills/recoup-catalog-review-deal/references/methodology.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/methodology.md rename to skills/recoup-catalog-review-deal/references/methodology.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/normalization.md b/skills/recoup-catalog-review-deal/references/normalization.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/normalization.md rename to skills/recoup-catalog-review-deal/references/normalization.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/output-templates.md b/skills/recoup-catalog-review-deal/references/output-templates.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/output-templates.md rename to skills/recoup-catalog-review-deal/references/output-templates.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/post-close-admin.md b/skills/recoup-catalog-review-deal/references/post-close-admin.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/post-close-admin.md rename to skills/recoup-catalog-review-deal/references/post-close-admin.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/pro-performance-income.md b/skills/recoup-catalog-review-deal/references/pro-performance-income.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/pro-performance-income.md rename to skills/recoup-catalog-review-deal/references/pro-performance-income.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/recoup-api.md b/skills/recoup-catalog-review-deal/references/recoup-api.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/recoup-api.md rename to skills/recoup-catalog-review-deal/references/recoup-api.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/red-flags.md b/skills/recoup-catalog-review-deal/references/red-flags.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/red-flags.md rename to skills/recoup-catalog-review-deal/references/red-flags.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/rights-review.md b/skills/recoup-catalog-review-deal/references/rights-review.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/rights-review.md rename to skills/recoup-catalog-review-deal/references/rights-review.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/royalty-audit.md b/skills/recoup-catalog-review-deal/references/royalty-audit.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/royalty-audit.md rename to skills/recoup-catalog-review-deal/references/royalty-audit.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/seller-prep.md b/skills/recoup-catalog-review-deal/references/seller-prep.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/seller-prep.md rename to skills/recoup-catalog-review-deal/references/seller-prep.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/references/valuation-framework.md b/skills/recoup-catalog-review-deal/references/valuation-framework.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/references/valuation-framework.md rename to skills/recoup-catalog-review-deal/references/valuation-framework.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/_helpers.py b/skills/recoup-catalog-review-deal/scripts/_helpers.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/_helpers.py rename to skills/recoup-catalog-review-deal/scripts/_helpers.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/auto-column-map.py b/skills/recoup-catalog-review-deal/scripts/auto-column-map.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/auto-column-map.py rename to skills/recoup-catalog-review-deal/scripts/auto-column-map.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/build-deal-readiness.py b/skills/recoup-catalog-review-deal/scripts/build-deal-readiness.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/build-deal-readiness.py rename to skills/recoup-catalog-review-deal/scripts/build-deal-readiness.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/build-file-manifest.py b/skills/recoup-catalog-review-deal/scripts/build-file-manifest.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/build-file-manifest.py rename to skills/recoup-catalog-review-deal/scripts/build-file-manifest.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/build-manual-review-queue.py b/skills/recoup-catalog-review-deal/scripts/build-manual-review-queue.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/build-manual-review-queue.py rename to skills/recoup-catalog-review-deal/scripts/build-manual-review-queue.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/calculate-concentration.py b/skills/recoup-catalog-review-deal/scripts/calculate-concentration.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/calculate-concentration.py rename to skills/recoup-catalog-review-deal/scripts/calculate-concentration.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/calculate-nps-nls-bridge.py b/skills/recoup-catalog-review-deal/scripts/calculate-nps-nls-bridge.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/calculate-nps-nls-bridge.py rename to skills/recoup-catalog-review-deal/scripts/calculate-nps-nls-bridge.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/dataroom-hygiene-scan.py b/skills/recoup-catalog-review-deal/scripts/dataroom-hygiene-scan.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/dataroom-hygiene-scan.py rename to skills/recoup-catalog-review-deal/scripts/dataroom-hygiene-scan.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/extract-pdf-statement.py b/skills/recoup-catalog-review-deal/scripts/extract-pdf-statement.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/extract-pdf-statement.py rename to skills/recoup-catalog-review-deal/scripts/extract-pdf-statement.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/normalize-royalty-statement.py b/skills/recoup-catalog-review-deal/scripts/normalize-royalty-statement.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/normalize-royalty-statement.py rename to skills/recoup-catalog-review-deal/scripts/normalize-royalty-statement.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/run-deal-checks.py b/skills/recoup-catalog-review-deal/scripts/run-deal-checks.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/run-deal-checks.py rename to skills/recoup-catalog-review-deal/scripts/run-deal-checks.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/validate-dashboard.py b/skills/recoup-catalog-review-deal/scripts/validate-dashboard.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/validate-dashboard.py rename to skills/recoup-catalog-review-deal/scripts/validate-dashboard.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/validate-deal-workspace.py b/skills/recoup-catalog-review-deal/scripts/validate-deal-workspace.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/validate-deal-workspace.py rename to skills/recoup-catalog-review-deal/scripts/validate-deal-workspace.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/validate-evidence-ledger.py b/skills/recoup-catalog-review-deal/scripts/validate-evidence-ledger.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/validate-evidence-ledger.py rename to skills/recoup-catalog-review-deal/scripts/validate-evidence-ledger.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/validate-findings-evidence.py b/skills/recoup-catalog-review-deal/scripts/validate-findings-evidence.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/validate-findings-evidence.py rename to skills/recoup-catalog-review-deal/scripts/validate-findings-evidence.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/validate-normalized-ledger.py b/skills/recoup-catalog-review-deal/scripts/validate-normalized-ledger.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/validate-normalized-ledger.py rename to skills/recoup-catalog-review-deal/scripts/validate-normalized-ledger.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/validate-workspace-consistency.py b/skills/recoup-catalog-review-deal/scripts/validate-workspace-consistency.py similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/scripts/validate-workspace-consistency.py rename to skills/recoup-catalog-review-deal/scripts/validate-workspace-consistency.py diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/templates/assumptions.yaml b/skills/recoup-catalog-review-deal/templates/assumptions.yaml similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/templates/assumptions.yaml rename to skills/recoup-catalog-review-deal/templates/assumptions.yaml diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/templates/deal-workspace/assumptions.yaml b/skills/recoup-catalog-review-deal/templates/deal-workspace/assumptions.yaml similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/templates/deal-workspace/assumptions.yaml rename to skills/recoup-catalog-review-deal/templates/deal-workspace/assumptions.yaml diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/templates/deal-workspace/evidence-ledger.json b/skills/recoup-catalog-review-deal/templates/deal-workspace/evidence-ledger.json similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/templates/deal-workspace/evidence-ledger.json rename to skills/recoup-catalog-review-deal/templates/deal-workspace/evidence-ledger.json diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/templates/deal-workspace/findings.json b/skills/recoup-catalog-review-deal/templates/deal-workspace/findings.json similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/templates/deal-workspace/findings.json rename to skills/recoup-catalog-review-deal/templates/deal-workspace/findings.json diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/templates/deal-workspace/memos/financing-pack.md b/skills/recoup-catalog-review-deal/templates/deal-workspace/memos/financing-pack.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/templates/deal-workspace/memos/financing-pack.md rename to skills/recoup-catalog-review-deal/templates/deal-workspace/memos/financing-pack.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/templates/deal-workspace/memos/ic-memo.md b/skills/recoup-catalog-review-deal/templates/deal-workspace/memos/ic-memo.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/templates/deal-workspace/memos/ic-memo.md rename to skills/recoup-catalog-review-deal/templates/deal-workspace/memos/ic-memo.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/templates/deal-workspace/memos/seller-cleanup-report.md b/skills/recoup-catalog-review-deal/templates/deal-workspace/memos/seller-cleanup-report.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/templates/deal-workspace/memos/seller-cleanup-report.md rename to skills/recoup-catalog-review-deal/templates/deal-workspace/memos/seller-cleanup-report.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/templates/deal-workspace/missing-files.md b/skills/recoup-catalog-review-deal/templates/deal-workspace/missing-files.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/templates/deal-workspace/missing-files.md rename to skills/recoup-catalog-review-deal/templates/deal-workspace/missing-files.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/templates/evidence-ledger.json b/skills/recoup-catalog-review-deal/templates/evidence-ledger.json similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/templates/evidence-ledger.json rename to skills/recoup-catalog-review-deal/templates/evidence-ledger.json diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/templates/findings.json b/skills/recoup-catalog-review-deal/templates/findings.json similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/templates/findings.json rename to skills/recoup-catalog-review-deal/templates/findings.json diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/templates/memos/financing-pack.md b/skills/recoup-catalog-review-deal/templates/memos/financing-pack.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/templates/memos/financing-pack.md rename to skills/recoup-catalog-review-deal/templates/memos/financing-pack.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/templates/memos/ic-memo.md b/skills/recoup-catalog-review-deal/templates/memos/ic-memo.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/templates/memos/ic-memo.md rename to skills/recoup-catalog-review-deal/templates/memos/ic-memo.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/templates/memos/seller-cleanup-report.md b/skills/recoup-catalog-review-deal/templates/memos/seller-cleanup-report.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/templates/memos/seller-cleanup-report.md rename to skills/recoup-catalog-review-deal/templates/memos/seller-cleanup-report.md diff --git a/plugins/recoup-records/skills/recoup-catalog-review-deal/templates/missing-files.md b/skills/recoup-catalog-review-deal/templates/missing-files.md similarity index 100% rename from plugins/recoup-records/skills/recoup-catalog-review-deal/templates/missing-files.md rename to skills/recoup-catalog-review-deal/templates/missing-files.md diff --git a/plugins/recoup-records/skills/recoup-content-asset-pack/SKILL.md b/skills/recoup-content-asset-pack/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-asset-pack/SKILL.md rename to skills/recoup-content-asset-pack/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-content-asset-pack/references/account-resolver.md b/skills/recoup-content-asset-pack/references/account-resolver.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-asset-pack/references/account-resolver.md rename to skills/recoup-content-asset-pack/references/account-resolver.md diff --git a/plugins/recoup-records/skills/recoup-content-asset-pack/references/analyze-gate.md b/skills/recoup-content-asset-pack/references/analyze-gate.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-asset-pack/references/analyze-gate.md rename to skills/recoup-content-asset-pack/references/analyze-gate.md diff --git a/plugins/recoup-records/skills/recoup-content-asset-pack/references/content-api.md b/skills/recoup-content-asset-pack/references/content-api.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-asset-pack/references/content-api.md rename to skills/recoup-content-asset-pack/references/content-api.md diff --git a/plugins/recoup-records/skills/recoup-content-asset-pack/references/research-context.md b/skills/recoup-content-asset-pack/references/research-context.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-asset-pack/references/research-context.md rename to skills/recoup-content-asset-pack/references/research-context.md diff --git a/plugins/recoup-records/skills/recoup-content-asset-pack/references/song-sourcing.md b/skills/recoup-content-asset-pack/references/song-sourcing.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-asset-pack/references/song-sourcing.md rename to skills/recoup-content-asset-pack/references/song-sourcing.md diff --git a/plugins/recoup-records/skills/recoup-content-asset-pack/references/workspace-context.md b/skills/recoup-content-asset-pack/references/workspace-context.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-asset-pack/references/workspace-context.md rename to skills/recoup-content-asset-pack/references/workspace-context.md diff --git a/plugins/recoup-records/skills/recoup-content-make-graphics/SKILL.md b/skills/recoup-content-make-graphics/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-make-graphics/SKILL.md rename to skills/recoup-content-make-graphics/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-content-make-graphics/references/account-resolver.md b/skills/recoup-content-make-graphics/references/account-resolver.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-make-graphics/references/account-resolver.md rename to skills/recoup-content-make-graphics/references/account-resolver.md diff --git a/plugins/recoup-records/skills/recoup-content-make-graphics/references/analyze-gate.md b/skills/recoup-content-make-graphics/references/analyze-gate.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-make-graphics/references/analyze-gate.md rename to skills/recoup-content-make-graphics/references/analyze-gate.md diff --git a/plugins/recoup-records/skills/recoup-content-make-graphics/references/content-api.md b/skills/recoup-content-make-graphics/references/content-api.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-make-graphics/references/content-api.md rename to skills/recoup-content-make-graphics/references/content-api.md diff --git a/plugins/recoup-records/skills/recoup-content-make-graphics/references/research-context.md b/skills/recoup-content-make-graphics/references/research-context.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-make-graphics/references/research-context.md rename to skills/recoup-content-make-graphics/references/research-context.md diff --git a/plugins/recoup-records/skills/recoup-content-make-graphics/references/song-sourcing.md b/skills/recoup-content-make-graphics/references/song-sourcing.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-make-graphics/references/song-sourcing.md rename to skills/recoup-content-make-graphics/references/song-sourcing.md diff --git a/plugins/recoup-records/skills/recoup-content-make-graphics/references/workspace-context.md b/skills/recoup-content-make-graphics/references/workspace-context.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-make-graphics/references/workspace-context.md rename to skills/recoup-content-make-graphics/references/workspace-context.md diff --git a/plugins/recoup-records/skills/recoup-content-make-video/SKILL.md b/skills/recoup-content-make-video/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-make-video/SKILL.md rename to skills/recoup-content-make-video/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-content-make-video/references/account-resolver.md b/skills/recoup-content-make-video/references/account-resolver.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-make-video/references/account-resolver.md rename to skills/recoup-content-make-video/references/account-resolver.md diff --git a/plugins/recoup-records/skills/recoup-content-make-video/references/analyze-gate.md b/skills/recoup-content-make-video/references/analyze-gate.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-make-video/references/analyze-gate.md rename to skills/recoup-content-make-video/references/analyze-gate.md diff --git a/plugins/recoup-records/skills/recoup-content-make-video/references/content-api.md b/skills/recoup-content-make-video/references/content-api.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-make-video/references/content-api.md rename to skills/recoup-content-make-video/references/content-api.md diff --git a/plugins/recoup-records/skills/recoup-content-make-video/references/research-context.md b/skills/recoup-content-make-video/references/research-context.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-make-video/references/research-context.md rename to skills/recoup-content-make-video/references/research-context.md diff --git a/plugins/recoup-records/skills/recoup-content-make-video/references/song-sourcing.md b/skills/recoup-content-make-video/references/song-sourcing.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-make-video/references/song-sourcing.md rename to skills/recoup-content-make-video/references/song-sourcing.md diff --git a/plugins/recoup-records/skills/recoup-content-make-video/references/workspace-context.md b/skills/recoup-content-make-video/references/workspace-context.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-make-video/references/workspace-context.md rename to skills/recoup-content-make-video/references/workspace-context.md diff --git a/plugins/recoup-records/skills/recoup-content-reactive-post/SKILL.md b/skills/recoup-content-reactive-post/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-reactive-post/SKILL.md rename to skills/recoup-content-reactive-post/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-content-reactive-post/references/account-resolver.md b/skills/recoup-content-reactive-post/references/account-resolver.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-reactive-post/references/account-resolver.md rename to skills/recoup-content-reactive-post/references/account-resolver.md diff --git a/plugins/recoup-records/skills/recoup-content-reactive-post/references/analyze-gate.md b/skills/recoup-content-reactive-post/references/analyze-gate.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-reactive-post/references/analyze-gate.md rename to skills/recoup-content-reactive-post/references/analyze-gate.md diff --git a/plugins/recoup-records/skills/recoup-content-reactive-post/references/content-api.md b/skills/recoup-content-reactive-post/references/content-api.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-reactive-post/references/content-api.md rename to skills/recoup-content-reactive-post/references/content-api.md diff --git a/plugins/recoup-records/skills/recoup-content-reactive-post/references/research-context.md b/skills/recoup-content-reactive-post/references/research-context.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-reactive-post/references/research-context.md rename to skills/recoup-content-reactive-post/references/research-context.md diff --git a/plugins/recoup-records/skills/recoup-content-reactive-post/references/song-sourcing.md b/skills/recoup-content-reactive-post/references/song-sourcing.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-reactive-post/references/song-sourcing.md rename to skills/recoup-content-reactive-post/references/song-sourcing.md diff --git a/plugins/recoup-records/skills/recoup-content-reactive-post/references/workspace-context.md b/skills/recoup-content-reactive-post/references/workspace-context.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-reactive-post/references/workspace-context.md rename to skills/recoup-content-reactive-post/references/workspace-context.md diff --git a/plugins/recoup-records/skills/recoup-content-write-caption/SKILL.md b/skills/recoup-content-write-caption/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-write-caption/SKILL.md rename to skills/recoup-content-write-caption/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-content-write-caption/references/account-resolver.md b/skills/recoup-content-write-caption/references/account-resolver.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-write-caption/references/account-resolver.md rename to skills/recoup-content-write-caption/references/account-resolver.md diff --git a/plugins/recoup-records/skills/recoup-content-write-caption/references/analyze-gate.md b/skills/recoup-content-write-caption/references/analyze-gate.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-write-caption/references/analyze-gate.md rename to skills/recoup-content-write-caption/references/analyze-gate.md diff --git a/plugins/recoup-records/skills/recoup-content-write-caption/references/content-api.md b/skills/recoup-content-write-caption/references/content-api.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-write-caption/references/content-api.md rename to skills/recoup-content-write-caption/references/content-api.md diff --git a/plugins/recoup-records/skills/recoup-content-write-caption/references/research-context.md b/skills/recoup-content-write-caption/references/research-context.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-write-caption/references/research-context.md rename to skills/recoup-content-write-caption/references/research-context.md diff --git a/plugins/recoup-records/skills/recoup-content-write-caption/references/song-sourcing.md b/skills/recoup-content-write-caption/references/song-sourcing.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-write-caption/references/song-sourcing.md rename to skills/recoup-content-write-caption/references/song-sourcing.md diff --git a/plugins/recoup-records/skills/recoup-content-write-caption/references/workspace-context.md b/skills/recoup-content-write-caption/references/workspace-context.md similarity index 100% rename from plugins/recoup-records/skills/recoup-content-write-caption/references/workspace-context.md rename to skills/recoup-content-write-caption/references/workspace-context.md diff --git a/plugins/recoup-internal/skills/recoup-account-health-report/SKILL.md b/skills/recoup-internal-account-health-report/SKILL.md similarity index 95% rename from plugins/recoup-internal/skills/recoup-account-health-report/SKILL.md rename to skills/recoup-internal-account-health-report/SKILL.md index ad795ee..a5b9f61 100644 --- a/plugins/recoup-internal/skills/recoup-account-health-report/SKILL.md +++ b/skills/recoup-internal-account-health-report/SKILL.md @@ -1,6 +1,10 @@ --- -name: recoup-account-health-report +name: recoup-internal-account-health-report description: >- + INTERNAL — Recoup staff tooling, gated by the recoup-internal keyword. Invoke + ONLY when the request explicitly includes "recoup-internal" (e.g. + "recoup-internal account health for this account"). Never use for + customer-facing or artist requests. Produce an account-status / account-health snapshot for a Recoup account from an email, account ID, or artist name. Resolves the account, pulls its artists, socials, chats, scheduled tasks, sandboxes, credits and subscription, then diff --git a/plugins/recoup-internal/skills/recoup-account-health-report/scripts/generate_account_status.py b/skills/recoup-internal-account-health-report/scripts/generate_account_status.py similarity index 100% rename from plugins/recoup-internal/skills/recoup-account-health-report/scripts/generate_account_status.py rename to skills/recoup-internal-account-health-report/scripts/generate_account_status.py diff --git a/plugins/recoup-internal/skills/recoup-dev-issue-tracker/SKILL.md b/skills/recoup-internal-dev-issue-tracker/SKILL.md similarity index 93% rename from plugins/recoup-internal/skills/recoup-dev-issue-tracker/SKILL.md rename to skills/recoup-internal-dev-issue-tracker/SKILL.md index 245341b..839b1bb 100644 --- a/plugins/recoup-internal/skills/recoup-dev-issue-tracker/SKILL.md +++ b/skills/recoup-internal-dev-issue-tracker/SKILL.md @@ -1,6 +1,6 @@ --- -name: recoup-dev-issue-tracker -description: Write and maintain high-signal GitHub issues — especially long-lived tracking issues that coordinate multi-PR, multi-repo efforts — in the Recoupable house style. Use when the user says "write an issue", "create a tracking issue", "open an issue for this work", "update the issue", "check off / close out an item", "log what shipped on the issue", "maintain the issue", or when capturing a plan, cutover, migration, or cleanup that spans more than one PR. Covers structure, the Open→Done lifecycle, evidence/linking rules, and acceptance criteria. +name: recoup-internal-dev-issue-tracker +description: INTERNAL — Recoup staff tooling, gated by the recoup-internal keyword. Invoke ONLY when the request explicitly includes "recoup-internal" (e.g. "recoup-internal write a tracking issue"). Never use for customer-facing or artist requests. Write and maintain high-signal GitHub issues — especially long-lived tracking issues that coordinate multi-PR, multi-repo efforts — in the Recoupable house style. Use when the user says "write an issue", "create a tracking issue", "open an issue for this work", "update the issue", "check off / close out an item", "log what shipped on the issue", "maintain the issue", or when capturing a plan, cutover, migration, or cleanup that spans more than one PR. Covers structure, the Open→Done lifecycle, evidence/linking rules, and acceptance criteria. --- # Writing & Maintaining Issues diff --git a/plugins/recoup-internal/skills/recoup-dev-ship-issue/SKILL.md b/skills/recoup-internal-dev-ship-issue/SKILL.md similarity index 83% rename from plugins/recoup-internal/skills/recoup-dev-ship-issue/SKILL.md rename to skills/recoup-internal-dev-ship-issue/SKILL.md index c44afe6..3c01e23 100644 --- a/plugins/recoup-internal/skills/recoup-dev-ship-issue/SKILL.md +++ b/skills/recoup-internal-dev-ship-issue/SKILL.md @@ -1,17 +1,17 @@ --- -name: recoup-dev-ship-issue -description: Deliver a tracked GitHub issue end-to-end in the Recoupable house style — documentation-driven (OpenAPI/contract first), then test-driven (red→green) implementation that matches the docs, verified against the live preview deployment, with results posted to the PR. Use when the user says "implement this issue", "build out issue #N", "ship the issue", "do the implementation", "take this issue and build it", or hands you a tracking issue (usually from the recoup-dev-issue-tracker skill) to deliver. Covers docs-first ordering, the TDD loop, preview verification, the docs↔API↔reality reconciliation, and PR review hygiene. Pairs with the recoup-dev-issue-tracker skill (which writes the issue this one implements). +name: recoup-internal-dev-ship-issue +description: INTERNAL — Recoup staff tooling, gated by the recoup-internal keyword. Invoke ONLY when the request explicitly includes "recoup-internal" (e.g. "recoup-internal ship issue #N"). Never use for customer-facing or artist requests. Deliver a tracked GitHub issue end-to-end in the Recoupable house style — documentation-driven (OpenAPI/contract first), then test-driven (red→green) implementation that matches the docs, verified against the live preview deployment, with results posted to the PR. Use when the user says "implement this issue", "build out issue #N", "ship the issue", "do the implementation", "take this issue and build it", or hands you a tracking issue (usually from the recoup-internal-dev-issue-tracker skill) to deliver. Covers docs-first ordering, the TDD loop, preview verification, the docs↔API↔reality reconciliation, and PR review hygiene. Pairs with the recoup-internal-dev-issue-tracker skill (which writes the issue this one implements). --- # Implementing a Tracked Issue How we take a tracking issue from open to shipped at Recoupable. The bar: **the docs, the API, and the live preview all tell the same story — and every claim you make on the PR is something you actually ran.** No contract drift, no "should work," no untested assertions. -This is the delivery counterpart to the **recoup-dev-issue-tracker** skill: that one writes the issue; this one implements it. The gold-standard reference slice this skill is built from is [chat#1789](https://github.com/recoupable/chat/issues/1789) (the tracking issue) → [docs#236](https://github.com/recoupable/docs/pull/236) (contract) → [api#653](https://github.com/recoupable/api/pull/653) (implementation). Read those three together to see the whole loop. +This is the delivery counterpart to the **recoup-internal-dev-issue-tracker** skill: that one writes the issue; this one implements it. The gold-standard reference slice this skill is built from is [chat#1789](https://github.com/recoupable/chat/issues/1789) (the tracking issue) → [docs#236](https://github.com/recoupable/docs/pull/236) (contract) → [api#653](https://github.com/recoupable/api/pull/653) (implementation). Read those three together to see the whole loop. ## Prerequisite: implement against a real contract, not a vibe -Only start when the issue is a proper spec — it should carry a **Goal**, a **proposed contract** (endpoint, params, response shape), a **merge sequencing** block (docs → api), **Done-when** acceptance criteria, and **source references**. If it doesn't, stop and write/upgrade it with the **recoup-dev-issue-tracker** skill first. Implementing against a vague issue is how you ship the wrong thing. +Only start when the issue is a proper spec — it should carry a **Goal**, a **proposed contract** (endpoint, params, response shape), a **merge sequencing** block (docs → api), **Done-when** acceptance criteria, and **source references**. If it doesn't, stop and write/upgrade it with the **recoup-internal-dev-issue-tracker** skill first. Implementing against a vague issue is how you ship the wrong thing. ## The loop @@ -90,7 +90,7 @@ All three must agree before you call it done. This step is the entire point of t - Merge order is **docs → api** (the contract lands first). Honor hard dependencies (a database migration before the api that reads it). - **Never merge without explicit user approval.** When the user approves: merge, then promote per the repo flow (Recoupable: squash → `test`, then `test` → `main` via a release PR, then sync `test` with `main`), checking the release scope first. -- On merge, **update the tracking issue to Done** with the **recoup-dev-issue-tracker** skill — a closure note (PR links, ✅ ISO date, merge path, what shipped, and a Verified clause citing the live results), and check off the Done-when boxes you actually verified. +- On merge, **update the tracking issue to Done** with the **recoup-internal-dev-issue-tracker** skill — a closure note (PR links, ✅ ISO date, merge path, what shipped, and a Verified clause citing the live results), and check off the Done-when boxes you actually verified. ## Principles @@ -127,4 +127,4 @@ gh pr comment --repo recoupable/api --body-file results.md - [ ] Docs ↔ API ↔ live results **agree** (reconciled and re-pushed if not). - [ ] Verification **posted on the PR**; bot findings **triaged** (validated, not rubber-stamped). - [ ] No secret value echoed anywhere — env-var names only. -- [ ] On merge: promoted per the repo flow and the tracking issue moved to **Done** (recoup-dev-issue-tracker). +- [ ] On merge: promoted per the repo flow and the tracking issue moved to **Done** (recoup-internal-dev-issue-tracker). diff --git a/plugins/recoup-internal/skills/recoup-eval-skill-benchmark/SKILL.md b/skills/recoup-internal-eval-skill-benchmark/SKILL.md similarity index 96% rename from plugins/recoup-internal/skills/recoup-eval-skill-benchmark/SKILL.md rename to skills/recoup-internal-eval-skill-benchmark/SKILL.md index 35d6482..696decd 100644 --- a/plugins/recoup-internal/skills/recoup-eval-skill-benchmark/SKILL.md +++ b/skills/recoup-internal-eval-skill-benchmark/SKILL.md @@ -1,6 +1,10 @@ --- -name: recoup-eval-skill-benchmark +name: recoup-internal-eval-skill-benchmark description: >- + INTERNAL — Recoup staff tooling, gated by the recoup-internal keyword. Invoke + ONLY when the request explicitly includes "recoup-internal" (e.g. + "recoup-internal benchmark this skill pack"). Never use for customer-facing or + artist requests. Benchmark ANY plugin, skill pack, or skills folder against the current frontier of skill/plugin design (gstack, gbrain, compound-engineering, PM OS). Use whenever someone wants to grade, audit, review, or compare a skills diff --git a/skills/skill-pack-benchmark/references/frontier-benchmark.md b/skills/recoup-internal-eval-skill-benchmark/references/frontier-benchmark.md similarity index 99% rename from skills/skill-pack-benchmark/references/frontier-benchmark.md rename to skills/recoup-internal-eval-skill-benchmark/references/frontier-benchmark.md index 0d7e27a..8d5480e 100644 --- a/skills/skill-pack-benchmark/references/frontier-benchmark.md +++ b/skills/recoup-internal-eval-skill-benchmark/references/frontier-benchmark.md @@ -1,6 +1,6 @@ # The frontier bar — what makes a skill pack good (the standard to benchmark against) -This is the reference standard the `skill-pack-benchmark` skill scores any +This is the reference standard the `recoup-internal-eval-skill-benchmark` skill scores any plugin or skills folder against. It distills the confirmed patterns and **real, measured numbers** from the current frontier of skill/plugin design. It is domain-agnostic: the pack under review can be about engineering, music, diff --git a/plugins/recoup-internal/skills/recoup-eval-skill-benchmark/references/scorecard.md b/skills/recoup-internal-eval-skill-benchmark/references/scorecard.md similarity index 100% rename from plugins/recoup-internal/skills/recoup-eval-skill-benchmark/references/scorecard.md rename to skills/recoup-internal-eval-skill-benchmark/references/scorecard.md diff --git a/plugins/recoup-internal/skills/recoup-eval-skill-benchmark/scripts/measure.py b/skills/recoup-internal-eval-skill-benchmark/scripts/measure.py similarity index 99% rename from plugins/recoup-internal/skills/recoup-eval-skill-benchmark/scripts/measure.py rename to skills/recoup-internal-eval-skill-benchmark/scripts/measure.py index 69f0264..9437051 100644 --- a/plugins/recoup-internal/skills/recoup-eval-skill-benchmark/scripts/measure.py +++ b/skills/recoup-internal-eval-skill-benchmark/scripts/measure.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """Measure any skill pack (plugin or skills folder) against the frontier bar. -This is the *deterministic* half of the recoup-eval-skill-benchmark skill: it counts +This is the *deterministic* half of the recoup-internal-eval-skill-benchmark skill: it counts what can be counted exactly so the agent never eyeballs (and mis-guesses) the numbers. It is harness-agnostic and reads only the target you point it at — it makes no network calls and writes nothing except its report. diff --git a/plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/SKILL.md b/skills/recoup-internal-funnel-valuation-pipeline/SKILL.md similarity index 97% rename from plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/SKILL.md rename to skills/recoup-internal-funnel-valuation-pipeline/SKILL.md index 06d45cd..3ec5995 100644 --- a/plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/SKILL.md +++ b/skills/recoup-internal-funnel-valuation-pipeline/SKILL.md @@ -1,6 +1,10 @@ --- -name: recoup-funnel-valuation-pipeline +name: recoup-internal-funnel-valuation-pipeline description: >- + INTERNAL — Recoup staff tooling, gated by the recoup-internal keyword. Invoke + ONLY when the request explicitly includes "recoup-internal" (e.g. + "recoup-internal work the valuation funnel"). Never use for customer-facing or + artist requests. Work the Recoup "Valuation Leads" sales funnel in Attio — take an inbound catalog-valuation lead and progress it from "valuation ran" to "signed up for Pro." Researches who the lead is and their relationship to the artist they diff --git a/plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/fixtures/Neon-Tide-valuation.pdf b/skills/recoup-internal-funnel-valuation-pipeline/fixtures/Neon-Tide-valuation.pdf similarity index 100% rename from plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/fixtures/Neon-Tide-valuation.pdf rename to skills/recoup-internal-funnel-valuation-pipeline/fixtures/Neon-Tide-valuation.pdf diff --git a/plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/fixtures/example-lead.json b/skills/recoup-internal-funnel-valuation-pipeline/fixtures/example-lead.json similarity index 100% rename from plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/fixtures/example-lead.json rename to skills/recoup-internal-funnel-valuation-pipeline/fixtures/example-lead.json diff --git a/plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/fixtures/example-pipeline-brief.md b/skills/recoup-internal-funnel-valuation-pipeline/fixtures/example-pipeline-brief.md similarity index 100% rename from plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/fixtures/example-pipeline-brief.md rename to skills/recoup-internal-funnel-valuation-pipeline/fixtures/example-pipeline-brief.md diff --git a/plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/references/attio-funnel.md b/skills/recoup-internal-funnel-valuation-pipeline/references/attio-funnel.md similarity index 100% rename from plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/references/attio-funnel.md rename to skills/recoup-internal-funnel-valuation-pipeline/references/attio-funnel.md diff --git a/plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/references/qualification-rubric.md b/skills/recoup-internal-funnel-valuation-pipeline/references/qualification-rubric.md similarity index 100% rename from plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/references/qualification-rubric.md rename to skills/recoup-internal-funnel-valuation-pipeline/references/qualification-rubric.md diff --git a/plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/references/recoup-valuation-api.md b/skills/recoup-internal-funnel-valuation-pipeline/references/recoup-valuation-api.md similarity index 100% rename from plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/references/recoup-valuation-api.md rename to skills/recoup-internal-funnel-valuation-pipeline/references/recoup-valuation-api.md diff --git a/plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/scripts/fetch_catalog.py b/skills/recoup-internal-funnel-valuation-pipeline/scripts/fetch_catalog.py similarity index 100% rename from plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/scripts/fetch_catalog.py rename to skills/recoup-internal-funnel-valuation-pipeline/scripts/fetch_catalog.py diff --git a/plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/scripts/render_valuation_pdf.py b/skills/recoup-internal-funnel-valuation-pipeline/scripts/render_valuation_pdf.py similarity index 100% rename from plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/scripts/render_valuation_pdf.py rename to skills/recoup-internal-funnel-valuation-pipeline/scripts/render_valuation_pdf.py diff --git a/plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/templates/outreach-email.md b/skills/recoup-internal-funnel-valuation-pipeline/templates/outreach-email.md similarity index 100% rename from plugins/recoup-internal/skills/recoup-funnel-valuation-pipeline/templates/outreach-email.md rename to skills/recoup-internal-funnel-valuation-pipeline/templates/outreach-email.md diff --git a/plugins/recoup-internal/skills/recoup-social-ship-posts/SKILL.md b/skills/recoup-internal-social-ship-posts/SKILL.md similarity index 90% rename from plugins/recoup-internal/skills/recoup-social-ship-posts/SKILL.md rename to skills/recoup-internal-social-ship-posts/SKILL.md index 49c1caf..3be91b3 100644 --- a/plugins/recoup-internal/skills/recoup-social-ship-posts/SKILL.md +++ b/skills/recoup-internal-social-ship-posts/SKILL.md @@ -1,6 +1,6 @@ --- -name: recoup-social-ship-posts -description: Write and ship data-grounded LinkedIn and X (Twitter) posts for product announcements and artist highlights, and track how they perform. Use when the user wants to draft a LinkedIn or X post, announce a feature or launch, highlight an artist or a result, plan a content calendar, decide on a CTA, or asks "how did my last post do," "what should I post," "write a post about X," or "turn this into a post/thread." This is the post copy + publishing + measurement workflow; producing a demo video is a separate asset-production step. +name: recoup-internal-social-ship-posts +description: INTERNAL — Recoup staff tooling, gated by the recoup-internal keyword. Invoke ONLY when the request explicitly includes "recoup-internal" (e.g. "recoup-internal draft a launch post"). Never use for customer-facing or artist requests. Write and ship data-grounded LinkedIn and X (Twitter) posts for product announcements and artist highlights, and track how they perform. Use when the user wants to draft a LinkedIn or X post, announce a feature or launch, highlight an artist or a result, plan a content calendar, decide on a CTA, or asks "how did my last post do," "what should I post," "write a post about X," or "turn this into a post/thread." This is the post copy + publishing + measurement workflow; producing a demo video is a separate asset-production step. --- # Recoup Social — Ship Posts diff --git a/plugins/recoup-records/skills/recoup-platform-api-access/SKILL.md b/skills/recoup-platform-api-access/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-platform-api-access/SKILL.md rename to skills/recoup-platform-api-access/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-platform-build-workspace/SKILL.md b/skills/recoup-platform-build-workspace/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-platform-build-workspace/SKILL.md rename to skills/recoup-platform-build-workspace/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-platform-capture-lesson/SKILL.md b/skills/recoup-platform-capture-lesson/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-platform-capture-lesson/SKILL.md rename to skills/recoup-platform-capture-lesson/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-platform-connect-account/SKILL.md b/skills/recoup-platform-connect-account/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-platform-connect-account/SKILL.md rename to skills/recoup-platform-connect-account/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/SKILL.md b/skills/recoup-release-plan-rollout/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/SKILL.md rename to skills/recoup-release-plan-rollout/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/references/deliverables.md b/skills/recoup-release-plan-rollout/references/deliverables.md similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/references/deliverables.md rename to skills/recoup-release-plan-rollout/references/deliverables.md diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/references/endpoints.md b/skills/recoup-release-plan-rollout/references/endpoints.md similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/references/endpoints.md rename to skills/recoup-release-plan-rollout/references/endpoints.md diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/references/release-template.md b/skills/recoup-release-plan-rollout/references/release-template.md similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/references/release-template.md rename to skills/recoup-release-plan-rollout/references/release-template.md diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/references/release-workspace.md b/skills/recoup-release-plan-rollout/references/release-workspace.md similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/references/release-workspace.md rename to skills/recoup-release-plan-rollout/references/release-workspace.md diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/references/response-shapes.md b/skills/recoup-release-plan-rollout/references/response-shapes.md similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/references/response-shapes.md rename to skills/recoup-release-plan-rollout/references/response-shapes.md diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/references/section-guide.md b/skills/recoup-release-plan-rollout/references/section-guide.md similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/references/section-guide.md rename to skills/recoup-release-plan-rollout/references/section-guide.md diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/scripts/build_campaign_timeline.py b/skills/recoup-release-plan-rollout/scripts/build_campaign_timeline.py similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/scripts/build_campaign_timeline.py rename to skills/recoup-release-plan-rollout/scripts/build_campaign_timeline.py diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/scripts/validate_release.py b/skills/recoup-release-plan-rollout/scripts/validate_release.py similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/scripts/validate_release.py rename to skills/recoup-release-plan-rollout/scripts/validate_release.py diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/templates/release-workspace/RELEASE.md b/skills/recoup-release-plan-rollout/templates/release-workspace/RELEASE.md similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/templates/release-workspace/RELEASE.md rename to skills/recoup-release-plan-rollout/templates/release-workspace/RELEASE.md diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/templates/release-workspace/assumptions.yaml b/skills/recoup-release-plan-rollout/templates/release-workspace/assumptions.yaml similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/templates/release-workspace/assumptions.yaml rename to skills/recoup-release-plan-rollout/templates/release-workspace/assumptions.yaml diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/templates/release-workspace/brief/.gitkeep b/skills/recoup-release-plan-rollout/templates/release-workspace/brief/.gitkeep similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/templates/release-workspace/brief/.gitkeep rename to skills/recoup-release-plan-rollout/templates/release-workspace/brief/.gitkeep diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/templates/release-workspace/campaign/.gitkeep b/skills/recoup-release-plan-rollout/templates/release-workspace/campaign/.gitkeep similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/templates/release-workspace/campaign/.gitkeep rename to skills/recoup-release-plan-rollout/templates/release-workspace/campaign/.gitkeep diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/templates/release-workspace/reports/.gitkeep b/skills/recoup-release-plan-rollout/templates/release-workspace/reports/.gitkeep similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/templates/release-workspace/reports/.gitkeep rename to skills/recoup-release-plan-rollout/templates/release-workspace/reports/.gitkeep diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/templates/release-workspace/targeting/.gitkeep b/skills/recoup-release-plan-rollout/templates/release-workspace/targeting/.gitkeep similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/templates/release-workspace/targeting/.gitkeep rename to skills/recoup-release-plan-rollout/templates/release-workspace/targeting/.gitkeep diff --git a/plugins/recoup-records/skills/recoup-release-plan-rollout/templates/release-workspace/tracking/.gitkeep b/skills/recoup-release-plan-rollout/templates/release-workspace/tracking/.gitkeep similarity index 100% rename from plugins/recoup-records/skills/recoup-release-plan-rollout/templates/release-workspace/tracking/.gitkeep rename to skills/recoup-release-plan-rollout/templates/release-workspace/tracking/.gitkeep diff --git a/plugins/recoup-records/skills/recoup-release-track-drop/SKILL.md b/skills/recoup-release-track-drop/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-release-track-drop/SKILL.md rename to skills/recoup-release-track-drop/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-release-track-drop/references/response-shapes.md b/skills/recoup-release-track-drop/references/response-shapes.md similarity index 100% rename from plugins/recoup-records/skills/recoup-release-track-drop/references/response-shapes.md rename to skills/recoup-release-track-drop/references/response-shapes.md diff --git a/plugins/recoup-records/skills/recoup-research-artist-overview/SKILL.md b/skills/recoup-research-artist-overview/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-research-artist-overview/SKILL.md rename to skills/recoup-research-artist-overview/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-research-artist-overview/references/endpoints.md b/skills/recoup-research-artist-overview/references/endpoints.md similarity index 100% rename from plugins/recoup-records/skills/recoup-research-artist-overview/references/endpoints.md rename to skills/recoup-research-artist-overview/references/endpoints.md diff --git a/plugins/recoup-records/skills/recoup-research-artist-overview/references/response-shapes.md b/skills/recoup-research-artist-overview/references/response-shapes.md similarity index 100% rename from plugins/recoup-records/skills/recoup-research-artist-overview/references/response-shapes.md rename to skills/recoup-research-artist-overview/references/response-shapes.md diff --git a/plugins/recoup-records/skills/recoup-research-artist-overview/references/workflows.md b/skills/recoup-research-artist-overview/references/workflows.md similarity index 100% rename from plugins/recoup-records/skills/recoup-research-artist-overview/references/workflows.md rename to skills/recoup-research-artist-overview/references/workflows.md diff --git a/plugins/recoup-records/skills/recoup-research-find-contacts/SKILL.md b/skills/recoup-research-find-contacts/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-research-find-contacts/SKILL.md rename to skills/recoup-research-find-contacts/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-research-find-contacts/references/workflows.md b/skills/recoup-research-find-contacts/references/workflows.md similarity index 100% rename from plugins/recoup-records/skills/recoup-research-find-contacts/references/workflows.md rename to skills/recoup-research-find-contacts/references/workflows.md diff --git a/plugins/recoup-records/skills/recoup-research-find-talent/SKILL.md b/skills/recoup-research-find-talent/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-research-find-talent/SKILL.md rename to skills/recoup-research-find-talent/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-research-find-talent/references/workflows.md b/skills/recoup-research-find-talent/references/workflows.md similarity index 100% rename from plugins/recoup-records/skills/recoup-research-find-talent/references/workflows.md rename to skills/recoup-research-find-talent/references/workflows.md diff --git a/plugins/recoup-records/skills/recoup-research-playlist-targets/SKILL.md b/skills/recoup-research-playlist-targets/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-research-playlist-targets/SKILL.md rename to skills/recoup-research-playlist-targets/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-research-playlist-targets/references/workflows.md b/skills/recoup-research-playlist-targets/references/workflows.md similarity index 100% rename from plugins/recoup-records/skills/recoup-research-playlist-targets/references/workflows.md rename to skills/recoup-research-playlist-targets/references/workflows.md diff --git a/plugins/recoup-records/skills/recoup-research-the-web/SKILL.md b/skills/recoup-research-the-web/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-research-the-web/SKILL.md rename to skills/recoup-research-the-web/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-research-the-web/references/workflows.md b/skills/recoup-research-the-web/references/workflows.md similarity index 100% rename from plugins/recoup-records/skills/recoup-research-the-web/references/workflows.md rename to skills/recoup-research-the-web/references/workflows.md diff --git a/plugins/recoup-records/skills/recoup-research-weekly-brief/SKILL.md b/skills/recoup-research-weekly-brief/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-research-weekly-brief/SKILL.md rename to skills/recoup-research-weekly-brief/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-research-weekly-brief/references/workflows.md b/skills/recoup-research-weekly-brief/references/workflows.md similarity index 100% rename from plugins/recoup-records/skills/recoup-research-weekly-brief/references/workflows.md rename to skills/recoup-research-weekly-brief/references/workflows.md diff --git a/plugins/recoup-records/skills/recoup-roster-add-artist/SKILL.md b/skills/recoup-roster-add-artist/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-roster-add-artist/SKILL.md rename to skills/recoup-roster-add-artist/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-roster-add-artist/references/artist-example.md b/skills/recoup-roster-add-artist/references/artist-example.md similarity index 100% rename from plugins/recoup-records/skills/recoup-roster-add-artist/references/artist-example.md rename to skills/recoup-roster-add-artist/references/artist-example.md diff --git a/plugins/recoup-records/skills/recoup-roster-add-artist/references/artist-template.md b/skills/recoup-roster-add-artist/references/artist-template.md similarity index 100% rename from plugins/recoup-records/skills/recoup-roster-add-artist/references/artist-template.md rename to skills/recoup-roster-add-artist/references/artist-template.md diff --git a/plugins/recoup-records/skills/recoup-roster-add-artist/references/audience-template.md b/skills/recoup-roster-add-artist/references/audience-template.md similarity index 100% rename from plugins/recoup-records/skills/recoup-roster-add-artist/references/audience-template.md rename to skills/recoup-roster-add-artist/references/audience-template.md diff --git a/plugins/recoup-records/skills/recoup-roster-add-artist/references/release-template.md b/skills/recoup-roster-add-artist/references/release-template.md similarity index 100% rename from plugins/recoup-records/skills/recoup-roster-add-artist/references/release-template.md rename to skills/recoup-roster-add-artist/references/release-template.md diff --git a/plugins/recoup-records/skills/recoup-roster-list-artists/SKILL.md b/skills/recoup-roster-list-artists/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-roster-list-artists/SKILL.md rename to skills/recoup-roster-list-artists/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-roster-manage-artist/SKILL.md b/skills/recoup-roster-manage-artist/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-roster-manage-artist/SKILL.md rename to skills/recoup-roster-manage-artist/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-song-analyze-audio/SKILL.md b/skills/recoup-song-analyze-audio/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-song-analyze-audio/SKILL.md rename to skills/recoup-song-analyze-audio/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-song-analyze-audio/references/flamingo-api.md b/skills/recoup-song-analyze-audio/references/flamingo-api.md similarity index 100% rename from plugins/recoup-records/skills/recoup-song-analyze-audio/references/flamingo-api.md rename to skills/recoup-song-analyze-audio/references/flamingo-api.md diff --git a/plugins/recoup-records/skills/recoup-song-find-hook/SKILL.md b/skills/recoup-song-find-hook/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-song-find-hook/SKILL.md rename to skills/recoup-song-find-hook/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-song-find-hook/references/flamingo-api.md b/skills/recoup-song-find-hook/references/flamingo-api.md similarity index 100% rename from plugins/recoup-records/skills/recoup-song-find-hook/references/flamingo-api.md rename to skills/recoup-song-find-hook/references/flamingo-api.md diff --git a/plugins/recoup-records/skills/recoup-song-placement-pitch/SKILL.md b/skills/recoup-song-placement-pitch/SKILL.md similarity index 100% rename from plugins/recoup-records/skills/recoup-song-placement-pitch/SKILL.md rename to skills/recoup-song-placement-pitch/SKILL.md diff --git a/plugins/recoup-records/skills/recoup-song-placement-pitch/references/flamingo-api.md b/skills/recoup-song-placement-pitch/references/flamingo-api.md similarity index 100% rename from plugins/recoup-records/skills/recoup-song-placement-pitch/references/flamingo-api.md rename to skills/recoup-song-placement-pitch/references/flamingo-api.md diff --git a/skills/release-management/README.md b/skills/release-management/README.md deleted file mode 100644 index 48be2e6..0000000 --- a/skills/release-management/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Recoup Release Management Skill - -An LLM skill for managing music release campaigns using RELEASE.md documents as the single source of truth. - -## What It Does - -- **Creates** RELEASE.md documents for new releases -- **Updates** existing release documents as information becomes available -- **Generates** deliverables (DSP pitches, press one-sheets, production specs) -- **Infers** which artist and release you're discussing from context - -## Folder Structure - -The skill expects releases organized by artist: - -``` -[Label Name]/ -└── [Artist Name]/ - └── Releases/ - └── [Release Name]/ - └── RELEASE.md -``` - -## Usage - -> "Create a RELEASE.md for [Artist]'s new album '[Title]'" - -> "Update [Artist]'s RELEASE.md with this ISRC data" - -> "Add track metadata to [Artist]'s '[Title]' release" - -> "Generate a DSP pitch from [Artist]'s RELEASE.md" - -> "What's missing from [Artist]'s release doc before announcement?" - -## Files - -| File | Purpose | -|------|---------| -| `SKILL.md` | Core skill instructions | -| `references/release-template.md` | Blank template for new releases | -| `references/deliverables.md` | Output formats (DSP pitch, press one-sheet, etc.) | -| `references/section-guide.md` | Field-by-field guidance for each section | - -## RELEASE.md Sections - -| # | Section | -|---|---------| -| 1 | Project Snapshot | -| 2 | Release Identifiers & Metadata | -| 3 | Narrative & Positioning | -| 4 | Artist Background | -| 5 | Audience & Market Data | -| 6 | DSP & Streaming Strategy | -| 7 | Marketing Strategy | -| 8 | Social & Digital Marketing | -| 9 | PR & Media Relations | -| 10 | Visual & Creative Assets | -| 11 | Physical Production | -| 12 | Merch | -| 13 | Experiential & OOH | -| 14 | Touring & Live | -| 15 | Team Contacts | -| 16 | Budget Overview | -| 17 | Performance Tracking | -| 18 | Links & Resources Hub | diff --git a/skills/release-management/SKILL.md b/skills/release-management/SKILL.md deleted file mode 100644 index 6f0ba59..0000000 --- a/skills/release-management/SKILL.md +++ /dev/null @@ -1,218 +0,0 @@ ---- -name: release-management -description: Manage music releases using RELEASE.md documents stored in releases/{release-slug}/ within an artist workspace. Triggers when the user mentions an artist's album, EP, single, or project — or asks about release planning, DSP pitches, metadata, marketing, press materials, physical production, or tour coordination. First infer which artist and release the user means, then find or create the RELEASE.md. Use this skill to create, update, or pull data from release documents, and to generate deliverables like DSP pitches, press one-sheets, and production specs. ---- - -# Music Release Management - -Manage music release campaigns using RELEASE.md as the single source of truth. - -## Folder Structure - -Releases live inside an artist workspace under `releases/`. The full path from the sandbox root: - -``` -orgs/{org}/ -└── artists/ - └── {artist-slug}/ - └── releases/ - └── {release-slug}/ - └── RELEASE.md -``` - -Use lowercase-kebab-case for release slugs (e.g. `blue-slide-park`, `debut-ep`). - -**Example:** -``` -orgs/recoup-records/artists/gatsby-grace/releases/adhd-ep/ - └── RELEASE.md -``` - -## Step 1: Identify the Release - -When the user mentions a release, infer: - -1. **Organization** — From the sandbox structure or conversation context -2. **Artist** — From the current workspace, conversation history, or ask -3. **Release Name** — Album title, EP name, single title -4. **Release slug** — Derive from the name (e.g. "Blue Slide Park" → `blue-slide-park`) - -**If unclear, ask:** -> "Which artist and release are you referring to?" - -## Step 2: Check if RELEASE.md Exists - -Once artist and release are identified: - -``` -1. Navigate to: orgs/{org}/artists/{artist-slug}/releases/{release-slug}/ -2. Check if RELEASE.md exists -3. If YES → Read it and proceed -4. If NO → Ask: "No RELEASE.md found for [Release]. Should I create one?" -``` - -## Step 3: Create, Update, or Pull - -| User Intent | Action | -|-------------|--------| -| Discussing a release | Read RELEASE.md, use as context | -| Adding information | Update the relevant section(s) | -| Asking for a deliverable | Pull data from RELEASE.md, generate output | -| Starting a new release | Create folder structure + RELEASE.md from template | - -### Creating a New Release - -```bash -# 1. Create the folder structure (from sandbox root) -mkdir -p "orgs/{org}/artists/{artist-slug}/releases/{release-slug}" - -# 2. Create RELEASE.md from template -# 3. Fill Section 1 (Project Snapshot) first -``` - -### Updating an Existing Release - -1. Read the current RELEASE.md -2. Identify which section(s) need updates -3. Update only those sections -4. Note changes in Document History (Section 18) - -## Core Principles - -1. **Never fabricate data** — Leave sections blank if information is missing -2. **Be proactive** — Fill sections as information becomes available -3. **Respect sharing tags** — `[INTERNAL]`, `[SHAREABLE]`, `[OPS]` control distribution -4. **One document per release** — All release info lives in RELEASE.md -5. **Always confirm the release** — Before making changes, confirm which release. - -## Sharing Tags - -| Tag | Meaning | -|-----|---------| -| `[INTERNAL]` | Scrub before sharing externally | -| `[SHAREABLE]` | Safe for publicists, DSPs, management, agents | -| `[OPS]` | Operations/production team reference | - -## Document Sections - -| Section | Purpose | Sharing | -|---------|---------|-------| -| 1. Project Snapshot | Core release info | SHAREABLE | -| 2. Release Identifiers & Metadata | UPCs, ISRCs, track data | OPS | -| 3. Narrative & Positioning | Pitch, story, comparables | SHAREABLE | -| 4. Artist Background | Bio, streaming history | SHAREABLE | -| 5. Audience & Market Data | Demographics, geo data | SHAREABLE | -| 6. DSP & Streaming Strategy | Pitches, playlist targets | SHAREABLE | -| 7. Marketing Strategy | Campaign goals, KPIs | INTERNAL | -| 8. Social & Digital Marketing | Organic, paid, influencer | INTERNAL | -| 9. PR & Media Relations | Press targets, materials | SHAREABLE | -| 10. Visual & Creative Assets | Artwork, videos, canvases | SHAREABLE | -| 11. Physical Production | Vinyl, CD, cassette specs | OPS/INTERNAL | -| 12. Merch | Items, strategy | INTERNAL | -| 13. Experiential & OOH | Events, billboards | INTERNAL | -| 14. Touring & Live | Dates, venues, routing | SHAREABLE | -| 15. Team Contacts | All stakeholders | INTERNAL | -| 16. Budget Overview | Allocated/spent/remaining | INTERNAL | -| 17. Performance Tracking | Weekly KPIs, learnings | INTERNAL | -| 18. Links & Resources Hub | All asset links | — | - -## Generating Deliverables - -See `references/deliverables.md` for output patterns: - -- **DSP Pitch** — Pull from Sections 1, 3, 4, 5, 6 -- **Press One-Sheet** — Pull from Sections 1, 3, 4, 9, 10 -- **Physical Production Spec** — Pull from Sections 2, 11 -- **Marketing Brief** — Pull from Sections 1, 3, 5, 6, 7, 8 -- **Tour Marketing Brief** — Pull from Sections 1, 3, 5, 14 - -When generating any deliverable: -1. Check RELEASE.md for required data -2. Identify missing fields -3. Request missing info from user OR generate with gaps noted -4. Format per deliverable spec - -## Template - -The full release template is in `references/release-template.md`. Copy this file to start a new release. - -## Section Deep-Dive - -See `references/section-guide.md` for detailed guidance on each section, including: -- What each field means -- Common data sources -- Best practices for filling out -- Red flags to watch for - -## Workflows - -### New Release Setup - -1. Copy template → RELEASE.md -2. Fill Section 1 (Project Snapshot) -3. Fill Section 2.2 (Track Metadata) as available -4. Draft Section 3 (Narrative & Positioning) -5. Pull artist data for Sections 4-5 -6. Build DSP pitch (Section 6) -7. Continue through remaining sections as timeline progresses - -### Pre-Release Checklist - -Before announcement, verify these sections are complete: - -- [ ] Section 1: All dates confirmed -- [ ] Section 2: UPC assigned, ISRCs for all tracks -- [ ] Section 3: Pitch and narrative finalized -- [ ] Section 6: DSP pitch submitted -- [ ] Section 9: Press materials ready -- [ ] Section 10: All visual assets delivered - -### Release Week Checklist - -- [ ] Section 6: Pre-save links live -- [ ] Section 7: Phase 2 actions executing -- [ ] Section 8: Paid ads launched -- [ ] Section 9: Press embargo lifted -- [ ] Section 17: Tracking dashboard ready - -### Post-Release - -- [ ] Section 17: Weekly KPIs logged -- [ ] Section 17: Wins and learnings documented -- [ ] Document History updated - -## Example Interactions - -### Creating a new release -> **User:** "Create a RELEASE.md for the new album 'Decisions'" -> -> **Process:** -> 1. Release = "Decisions", slug = `decisions` -> 2. Create `releases/decisions/RELEASE.md` from template -> 3. Ask: "What's the release date?" (to fill Section 1) - -### Adding metadata -> **User:** "Update the 'Sunrise' RELEASE.md with these ISRCs" -> -> **Process:** -> 1. Release = "Sunrise", slug = `sunrise` -> 2. Open `releases/sunrise/RELEASE.md` -> 3. Update Section 2.2 with ISRC data -> 4. If file not found → "No RELEASE.md for 'Sunrise'. Should I create one?" - -### Generating a deliverable -> **User:** "Generate a DSP pitch from the Midnights RELEASE.md" -> -> **Process:** -> 1. Read `releases/midnights/RELEASE.md` -> 2. Pull data from Sections 1, 3, 4, 5, 6 -> 3. Format per `deliverables.md` spec -> 4. If missing fields → "Missing [fields]. Proceed with gaps noted?" - -### Checking release status -> **User:** "What's missing from the 'For All The Dogs' release doc?" -> -> **Process:** -> 1. Read `releases/for-all-the-dogs/RELEASE.md` -> 2. Run through Pre-Release Checklist -> 3. Report incomplete sections diff --git a/skills/release-management/references/deliverables.md b/skills/release-management/references/deliverables.md deleted file mode 100644 index ec89a33..0000000 --- a/skills/release-management/references/deliverables.md +++ /dev/null @@ -1,368 +0,0 @@ -# Deliverables Guide - -This guide covers how to generate common deliverables from a RELEASE.md document. - ---- - -## Table of Contents - -1. [DSP Pitch](#dsp-pitch) -2. [Press One-Sheet](#press-one-sheet) -3. [Physical Production Spec](#physical-production-spec) -4. [Marketing Brief](#marketing-brief) -5. [Tour Marketing Brief](#tour-marketing-brief) -6. [Shareable Export](#shareable-export) -7. [Weekly Report](#weekly-report) - ---- - -## DSP Pitch - -**Purpose:** Submit to Spotify For Artists, Apple Music for Artists, Amazon Music for Artists - -**Source Sections:** 1, 3, 4, 5, 6 - -### Spotify For Artists Format (500 character limit) - -Pull from: -- Section 1: Artist name, project title, release date, genre -- Section 3.1: Elevator pitch -- Section 4.2: Key streaming stats, certifications -- Section 5.2: Top cities/countries -- Section 6.2: Focus track and why - -**Template:** - -``` -[ARTIST] releases [PROJECT TITLE] on [DATE]. [2-3 SENTENCE PITCH FROM 3.1]. - -Key stats: [STREAMING HIGHLIGHTS FROM 4.2] -Top markets: [TOP 3 CITIES FROM 5.2] -Focus track: "[TRACK NAME]" - [RATIONALE FROM 6.2] - -Genre: [PRIMARY GENRE] / [SUBGENRE] -``` - -### Apple Music Format (No strict limit) - -Same content but can expand narrative from Section 3.2. - -### Required Fields Checklist - -- [ ] Artist name -- [ ] Project title -- [ ] Release date -- [ ] Genre(s) -- [ ] Pitch (2-3 sentences) -- [ ] Key streaming stats -- [ ] Top markets -- [ ] Focus track + rationale - ---- - -## Press One-Sheet - -**Purpose:** Media kit for publicists, journalists, outlets - -**Source Sections:** 1, 3, 4, 9, 10 - -### Structure - -``` -═══════════════════════════════════════ -[ARTIST NAME] -[PROJECT TITLE] -[RELEASE DATE] -═══════════════════════════════════════ - -ABOUT THE RELEASE -[Extended narrative from Section 3.2] - -PROJECT HIGHLIGHTS -[Bullets from Section 3.3] - -FOR FANS OF -[Comparables from Section 3.5] - -ABOUT THE ARTIST -[Bio from Section 4.1] - -STREAMING HIGHLIGHTS -[Stats from Section 4.2] - -PRESS HIGHLIGHTS -[Coverage from Section 4.3] - -ASSETS -Artwork: [Link from 10.2] -Press Photos: [Link from 9.3] -EPK: [Link from 9.3] - -CONTACT -Publicist: [Public-facing press contact name and email] - -═══════════════════════════════════════ -``` - -### Required Fields Checklist - -- [ ] Artist name -- [ ] Project title -- [ ] Release date -- [ ] Extended narrative -- [ ] Artist bio -- [ ] At least 3 streaming stats -- [ ] Press photo link -- [ ] Publicist contact - ---- - -## Physical Production Spec - -**Purpose:** Manufacturing partner briefing (vinyl plant, CD plant) - -**Source Sections:** 2, 11 - -### Structure - -``` -═══════════════════════════════════════ -PHYSICAL PRODUCTION SPECIFICATION -[PROJECT TITLE] by [ARTIST NAME] -═══════════════════════════════════════ - -IDENTIFIERS -UPC: [From 2.1] -Street Date: [From Section 1] - -AUDIO -Track count: [Count from 2.2] -Total runtime: [Sum of durations from 2.2] -Master WAVs: [Link from 2.3] - -TRACKLIST -[Full tracklist from 2.2 with ISRCs] - -VINYL SPECIFICATIONS -Quantity: [From 11.1] -Color: [From 11.1] -Packaging: [From 11.2] -Inserts: [From 11.2] - -CD SPECIFICATIONS -Quantity: [From 11.1] -[Additional specs] - -CASSETTE SPECIFICATIONS -Quantity: [From 11.1] -Color: [From 11.1] - -TIMELINE -[Full timeline from 11.3] - -PARTNERS -Manufacturer: [From 11.3] -Fulfillment: [From 11.3] - -═══════════════════════════════════════ -``` - -### Required Fields Checklist - -- [ ] UPC -- [ ] Street date -- [ ] Full tracklist with ISRCs -- [ ] Master audio link -- [ ] Format quantities -- [ ] Packaging specs -- [ ] Manufacturing timeline -- [ ] Partner contacts - ---- - -## Marketing Brief - -**Purpose:** Internal campaign planning document - -**Source Sections:** 1, 3, 5, 6, 7, 8 - -### Structure - -``` -═══════════════════════════════════════ -MARKETING BRIEF -[PROJECT TITLE] by [ARTIST NAME] -Campaign Period: [ANNOUNCEMENT DATE] to [RELEASE DATE + 4 WEEKS] -═══════════════════════════════════════ - -RELEASE OVERVIEW -[Section 1 snapshot] - -THE STORY -[Section 3.1 pitch] -[Section 3.3 highlights] - -TARGET AUDIENCE -Demographics: [From 5.3] -Top markets: [From 5.2] -Core fanbase: [From 5.4] - -CAMPAIGN GOALS -[From 7.1] - -KEY KPIS -[Table from 7.2] - -ROLLOUT PHASES -[From 7.3 - all three phases] - -SOCIAL STRATEGY -[From 8.1 and 8.2] - -PAID MEDIA PLAN -[From 8.4] - -TOTAL BUDGET: [From 8.4] - -═══════════════════════════════════════ -``` - ---- - -## Tour Marketing Brief - -**Purpose:** Geo-targeted marketing around tour dates - -**Source Sections:** 1, 3, 5, 14 - -### Structure - -``` -═══════════════════════════════════════ -TOUR MARKETING BRIEF -[TOUR NAME] -[ARTIST NAME] - [PROJECT TITLE] -═══════════════════════════════════════ - -TOUR OVERVIEW -Dates: [From 14.1] -Markets: [Count of cities from 14.2] -Agent: [From 14.1] - -RELEASE TIE-IN -Release date: [From Section 1] -Pitch: [From 3.1] - -MARKET PRIORITIES -[Based on overlap between 14.2 routing and 5.2 top cities] - -HIGH-PRIORITY MARKETS (Top streaming + show date) -[List] - -ROUTING -[Full table from 14.2] - -GEO-TARGETING STRATEGY -[From 14.3] - -LOCAL CONTENT PLAN -[From 14.3] - -═══════════════════════════════════════ -``` - ---- - -## Shareable Export - -**Purpose:** Version of RELEASE.md safe for external partners - -**Process:** - -1. Copy full RELEASE.md -2. Remove all sections marked `[INTERNAL]`: - - Section 7: Marketing Strategy - - Section 8: Social & Digital Marketing - - Section 12: Merch - - Section 13: Experiential & OOH - - Section 15: Team Contacts - - Section 16: Budget Overview - - Section 17: Performance Tracking -3. Remove `[INTERNAL]` subsections within `[OPS]` sections: - - Section 2.3: Audio Asset Links -4. Strip any remaining `[OPS / INTERNAL]` or `[INTERNAL]` markers from kept sections -5. Keep all `[SHAREABLE]` and `[OPS]` sections -6. Rename to `RELEASE-EXTERNAL.md` - ---- - -## Weekly Report - -**Purpose:** Status update for stakeholders - -**Source Sections:** 17, 7.2 - -### Structure - -``` -═══════════════════════════════════════ -WEEKLY CAMPAIGN REPORT -[PROJECT TITLE] by [ARTIST NAME] -Week [N] | [DATE RANGE] -═══════════════════════════════════════ - -KPI SUMMARY -[Week's row from 17.1 vs targets from 7.2] - -| Metric | This Week | Target | Status | -|--------|-----------|--------|--------| -| Spotify Streams | ___ | ___ | 🟢/🟡/🔴 | -| Apple Streams | ___ | ___ | 🟢/🟡/🔴 | -| TikTok Views | ___ | ___ | 🟢/🟡/🔴 | -| IG Engagement | ___ | ___ | 🟢/🟡/🔴 | - -WINS THIS WEEK -[From 17.2] - -ADJUSTMENTS -[From 17.2] - -NEXT WEEK PRIORITIES -[Based on current phase from 7.3] - -═══════════════════════════════════════ -``` - ---- - -## Generating Any Deliverable - -### Workflow - -1. **Identify the deliverable type** from this guide -2. **Check source sections** for required data -3. **Note missing fields** - request from user or mark as TBD -4. **Generate output** following the template structure -5. **Verify sharing tags** — don't include [INTERNAL] data in external docs - -### Handling Missing Data - -When generating a deliverable with missing fields: - -``` -⚠️ MISSING INFORMATION - -The following fields are required but not in RELEASE.md: -- [Field 1] -- [Field 2] - -Please provide this information or confirm you want to proceed with gaps. -``` - -### Custom Deliverables - -For deliverables not covered here: -1. Identify which sections contain relevant data -2. Check sharing tags for each section -3. Structure output appropriate to audience -4. Request user approval before sharing externally diff --git a/skills/release-management/references/release-template.md b/skills/release-management/references/release-template.md deleted file mode 100644 index fae0489..0000000 --- a/skills/release-management/references/release-template.md +++ /dev/null @@ -1,545 +0,0 @@ -# RELEASE.MD - -**Last Updated:** [DATE] - -> **Sharing Tags:** `[INTERNAL]` scrub before external sharing · `[SHAREABLE]` safe for DSPs, press, partners · `[OPS]` operations reference - ---- - -## Section 1: Project Snapshot [SHAREABLE] - -| Field | Value | -|-------|-------| -| Artist Name | _______________ | -| Project Title | _______________ | -| Release Date (Digital) | _______________ | -| Release Date (Physical) | _______________ | -| Announcement Date | _______________ | -| Format | Single / EP / Album / Deluxe / Mixtape | -| Territory | WW / US Only / Specific Territories | -| Primary Genre | _______________ | -| Subgenre(s) | _______________ | -| Explicit / Clean | _______________ | -| Label | _______________ | -| Distributor | _______________ | - ---- - -## Section 2: Release Identifiers & Metadata [OPS] - -### 2.1 Digital Identifiers - -| Identifier | Value | -|------------|-------| -| UPC | _______________ | -| Spotify URI | _______________ | -| Apple Music ID | _______________ | -| Amazon ASIN | _______________ | -| YouTube Asset ID | _______________ | - -### 2.2 Track-Level Metadata - -**Track 01:** -| Field | Value | -|-------|-------| -| Title | _______________ | -| ISRC | _______________ | -| Duration | _______________ | -| Explicit? | _______________ | -| Writers | _______________ | -| Producers | _______________ | -| Features | _______________ | - -**Track 02:** -| Field | Value | -|-------|-------| -| Title | _______________ | -| ISRC | _______________ | -| Duration | _______________ | -| Explicit? | _______________ | -| Writers | _______________ | -| Producers | _______________ | -| Features | _______________ | - -_(Add more tracks as needed)_ - -### 2.3 Audio Asset Links [INTERNAL] - -| Asset | Link | -|-------|------| -| Master WAVs | [Link] | -| Instrumentals | [Link] | -| Stems (if applicable) | [Link] | -| Radio Edits | [Link] | - ---- - -## Section 3: Narrative & Positioning [SHAREABLE] - -### 3.1 The Pitch (Elevator Version) - -_2-3 sentences capturing the essence of this release._ - -[WRITE PITCH HERE] - -### 3.2 Extended Narrative - -_The fuller story. Context, artist arc, meaning._ - -[WRITE NARRATIVE HERE] - -### 3.3 Project Highlights / Tentpoles - -- _______________ -- _______________ -- _______________ - -### 3.4 Mood & Keywords - -| Category | Value | -|----------|-------| -| Mood Tags | _______________ | -| Vibe Keywords | _______________ | -| Activity/Use Case Tags | _______________ | - -### 3.5 Comparables - -| Category | Value | -|----------|-------| -| For Fans Of (RIYL) | _______________ | -| Sonic References | _______________ | -| Competitive Positioning | _______________ | - ---- - -## Section 4: Artist Background [SHAREABLE] - -### 4.1 Artist Bio (150-200 words) - -[WRITE BIO HERE] - -### 4.2 Sales & Streaming History - -| Platform | Stats | -|----------|-------| -| Spotify | _______________ | -| Apple Music | _______________ | -| Amazon Music | _______________ | -| Pandora | _______________ | -| YouTube | _______________ | - -| Metric | Value | -|--------|-------| -| Certifications | _______________ | -| Previous Editorial Support | _______________ | -| Notable Collaborations | _______________ | - -### 4.3 Press & Media Highlights - -| Publication | Headline | Date | Link | -|-------------|----------|------|------| -| _______________ | _______________ | _______________ | _______________ | -| _______________ | _______________ | _______________ | _______________ | -| _______________ | _______________ | _______________ | _______________ | - ---- - -## Section 5: Audience & Market Data [SHAREABLE] - -### 5.1 Current Audience Stats - -| Platform | Handle/Value | -|----------|--------------| -| Spotify Monthly Listeners | _______________ | -| Instagram | @_______________ | -| TikTok | @_______________ | -| YouTube | @_______________ | -| Twitter/X | @_______________ | -| Facebook | _______________ | -| VAULT/SMS List | _______________ | - -### 5.2 Geographic Data - -| Region | Top 5 | -|--------|-------| -| Top Streaming Cities (US) | 1.___ 2.___ 3.___ 4.___ 5.___ | -| Top Streaming Countries | 1.___ 2.___ 3.___ 4.___ 5.___ | - -### 5.3 Demographics - -| Age Range | Percentage | -|-----------|------------| -| 18-24 | ___% | -| 25-34 | ___% | -| 35+ | ___% | - -| Gender | Percentage | -|--------|------------| -| Female | ___% | -| Male | ___% | - -### 5.4 Core Fanbase Insights - -_Who are they? What do they care about?_ - -[WRITE INSIGHTS HERE] - ---- - -## Section 6: DSP & Streaming Strategy [SHAREABLE] - -### 6.1 Spotify For Artists Pitch (500 char limit) - -[WRITE PITCH HERE] - -### 6.2 Focus Track(s) - -| Track | Title | Rationale | -|-------|-------|-----------| -| Primary Focus | _______________ | _______________ | -| Secondary Focus | _______________ | _______________ | - -### 6.3 Editorial Playlist Targets - -| Platform | Playlist | Priority | Rationale | -|----------|----------|----------|-----------| -| Spotify | _______________ | _______________ | _______________ | -| Apple Music | _______________ | _______________ | _______________ | -| Amazon Music | _______________ | _______________ | _______________ | -| YouTube Music | _______________ | _______________ | _______________ | - -### 6.4 Pre-Save Strategy - -| Element | Value | -|---------|-------| -| Pre-Save Link | _______________ | -| SmartURL | _______________ | -| Fan Opt-In | Yes / No | -| Pre-Save Incentive | _______________ | - ---- - -## Section 7: Marketing Strategy [INTERNAL] - -### 7.1 Campaign Goals - -- _______________ -- _______________ -- _______________ - -### 7.2 Key KPIs - -| Metric | Current | Target | -|--------|---------|--------| -| Spotify Streams (Week 1) | ___ | ___ | -| Spotify Follower Growth | ___ | ___ | -| Pre-Saves | ___ | ___ | -| Instagram Engagement | ___ | ___ | -| TikTok Views | ___ | ___ | - -### 7.3 Rollout Phases - -**PHASE 1: TEASE & ANNOUNCE** -| Field | Value | -|-------|-------| -| Dates | [Start] to [End] | -| Goal | _______________ | -| Action Items | _______________ | - -**PHASE 2: RELEASE WEEK** -| Field | Value | -|-------|-------| -| Dates | [Start] to [End] | -| Goal | _______________ | -| Action Items | _______________ | - -**PHASE 3: SUSTAIN & WORK** -| Field | Value | -|-------|-------| -| Dates | [Start] to [End] | -| Goal | _______________ | -| Action Items | _______________ | - ---- - -## Section 8: Social & Digital Marketing [INTERNAL] - -### 8.1 Organic Social Strategy - -| Element | Value | -|---------|-------| -| Primary Platforms | _______________ | -| Content Pillars | _______________ | -| Posting Cadence | _______________ | -| Key Hooks/Angles | _______________ | - -### 8.2 TikTok & UGC Strategy - -| Element | Value | -|---------|-------| -| UGC Hook(s) | _______________ | -| Trend/Sound Strategy | _______________ | -| Prompt-Based Storytelling Ideas | _______________ | - -### 8.3 Influencer & Community Marketing - -| Influencer/Creator | Platform | Followers | Status | Notes | -|--------------------|----------|-----------|--------|-------| -| _______________ | _______________ | _______________ | _______________ | _______________ | -| _______________ | _______________ | _______________ | _______________ | _______________ | - -### 8.4 Paid Advertising - -| Platform | Budget | Objective | Targeting | Dates | -|----------|--------|-----------|-----------|-------| -| TikTok | $_____ | _______________ | _______________ | _______________ | -| Meta | $_____ | _______________ | _______________ | _______________ | -| YouTube | $_____ | _______________ | _______________ | _______________ | -| Spotify Ad Studio | $_____ | _______________ | _______________ | _______________ | - -**Total Campaign Budget:** $_______________ - ---- - -## Section 9: PR & Media Relations [SHAREABLE] - -### 9.1 Press Strategy - -| Element | Value | -|---------|-------| -| Press Angle | _______________ | -| Embargo Date | _______________ | -| Exclusive Offer | _______________ | - -### 9.2 Press Targets - -| Outlet | Contact | Priority | Status | Notes | -|--------|---------|----------|--------|-------| -| _______________ | _______________ | _______________ | _______________ | _______________ | -| _______________ | _______________ | _______________ | _______________ | _______________ | - -### 9.3 Press Materials - -| Material | Link | -|----------|------| -| Press Release | [Link] | -| EPK / One-Sheet | [Link] | -| Press Photos | [Link] | -| Album Artwork (Hi-Res) | [Link] | - ---- - -## Section 10: Visual & Creative Assets [SHAREABLE] - -### 10.1 Visual Strategy - -| Element | Value | -|---------|-------| -| Creative Concept | _______________ | -| Aesthetic/Mood | _______________ | - -### 10.2 Asset Inventory - -| Asset | Link | Status | -|-------|------|--------| -| Album Artwork | _______________ | _______________ | -| Press Photos | _______________ | _______________ | -| Music Video | _______________ | _______________ | -| Lyric Video | _______________ | _______________ | -| Visualizer | _______________ | _______________ | -| Spotify Canvas | _______________ | _______________ | -| Social Templates | _______________ | _______________ | - ---- - -## Section 11: Physical Production [OPS / INTERNAL] - -### 11.1 Physical Formats - -| Format | Quantity | Color/Variant | Status | -|--------|----------|---------------|--------| -| Vinyl | ___ | _______________ | _______________ | -| CD | ___ | — | _______________ | -| Cassette | ___ | _______________ | _______________ | - -### 11.2 Packaging Specs - -| Element | Value | -|---------|-------| -| Vinyl Packaging | _______________ | -| Inserts | _______________ | -| Special Edition Details | _______________ | - -### 11.3 Manufacturing Timeline - -| Milestone | Date | Status | -|-----------|------|--------| -| Audio Masters to Plant | _______________ | _______________ | -| Artwork to Plant | _______________ | _______________ | -| Test Pressing Approval | _______________ | _______________ | -| Manufacturing Complete | _______________ | _______________ | -| Ship to Distributor | _______________ | _______________ | -| D2C Pre-Order Launch | _______________ | _______________ | -| Street Date | _______________ | _______________ | - -| Partner | Name | -|---------|------| -| Manufacturer | _______________ | -| Fulfillment Partner | _______________ | - -### 11.4 D2C & Retail - -| Element | Value | -|---------|-------| -| D2C Store Link | _______________ | -| Bundles Offered | _______________ | - ---- - -## Section 12: Merch [INTERNAL] - -### 12.1 Merch Items - -| Item | Variants | Qty | Cost | Retail | Status | -|------|----------|-----|------|--------|--------| -| _______________ | _______________ | ___ | $___ | $___ | _______________ | -| _______________ | _______________ | ___ | $___ | $___ | _______________ | - -### 12.2 Merch Strategy - -| Element | Value | -|---------|-------| -| Bundling with Release | Yes / No | -| Exclusive Items | _______________ | -| Tour Merch Tie-In | _______________ | - ---- - -## Section 13: Experiential & OOH [INTERNAL] - -### 13.1 Out-of-Home Activations - -| Type | Location | Date | Budget | -|------|----------|------|--------| -| Billboards | _______________ | _______________ | $___ | -| Wild Posting | _______________ | _______________ | $___ | -| Guerrilla/Street | _______________ | _______________ | $___ | - -### 13.2 Events & Experiences - -| Event | Location | Date | Details | Status | -|-------|----------|------|---------|--------| -| _______________ | _______________ | _______________ | _______________ | _______________ | - -### 13.3 Brand Partnerships - -| Brand | Type | Status | Contact | Notes | -|-------|------|--------|---------|-------| -| _______________ | _______________ | _______________ | _______________ | _______________ | - ---- - -## Section 14: Touring & Live [SHAREABLE] - -### 14.1 Tour Overview - -| Element | Value | -|---------|-------| -| Tour Name | _______________ | -| Tour Dates | [Start] to [End] | -| Booking Agent | _______________ | -| Tour Manager | _______________ | - -### 14.2 Tour Routing - -| Date | City | Venue | Capacity | Ticket Link | Status | -|------|------|-------|----------|-------------|--------| -| _______________ | _______________ | _______________ | ___ | _______________ | _______________ | -| _______________ | _______________ | _______________ | ___ | _______________ | _______________ | - -### 14.3 Tour Marketing Tie-In - -| Element | Value | -|---------|-------| -| Geo-Targeted Ad Markets | _______________ | -| City-Specific Content | _______________ | -| On-Site Activations | _______________ | - ---- - -## Section 15: Team Contacts [INTERNAL] - -| Role | Name | Email | Phone | -|------|------|-------|-------| -| Marketing Lead | ___ | ___ | ___ | -| Ops Lead | ___ | ___ | ___ | -| A&R | ___ | ___ | ___ | -| Management | ___ | ___ | ___ | -| Publicist | ___ | ___ | ___ | -| Booking Agent | ___ | ___ | ___ | -| Digital Marketing Partner | ___ | ___ | ___ | -| Radio Promo | ___ | ___ | ___ | - ---- - -## Section 16: Budget Overview [INTERNAL] - -| Category | Allocated | Spent | Remaining | -|----------|-----------|-------|-----------| -| Paid Media | $___ | $___ | $___ | -| Influencer | $___ | $___ | $___ | -| PR/Press | $___ | $___ | $___ | -| Video Production | $___ | $___ | $___ | -| Physical Production | $___ | $___ | $___ | -| OOH/Experiential | $___ | $___ | $___ | -| Merch | $___ | $___ | $___ | -| Other | $___ | $___ | $___ | -| **TOTAL** | **$___** | **$___** | **$___** | - ---- - -## Section 17: Performance Tracking [INTERNAL] - -### 17.1 Weekly KPI Tracking - -| Week | Spotify | Apple | TikTok | IG | -|------|---------|-------|--------|-----| -| Week 1 | ___ | ___ | ___ | ___ | -| Week 2 | ___ | ___ | ___ | ___ | -| Week 3 | ___ | ___ | ___ | ___ | -| Week 4 | ___ | ___ | ___ | ___ | - -### 17.2 Key Wins & Learnings - -| Category | Notes | -|----------|-------| -| Wins | _______________ | -| Adjustments Made | _______________ | -| Learnings for Future Releases | _______________ | - ---- - -## Section 18: Links & Resources Hub - -| Resource | Link | -|----------|------| -| Master Asset Folder | [Link] | -| SmartURL | [Link] | -| Pre-Save Link | [Link] | -| EPK | [Link] | -| Press Photos | [Link] | -| Audio Assets | [Link] | -| Video Assets | [Link] | -| Social Templates | [Link] | - ---- - -## Document History - -| Date | Updated By | Changes | -|------|------------|---------| -| ___ | ___ | ___ | - ---- - -For questions, contact [MARKETING LEAD EMAIL]. diff --git a/skills/release-management/references/section-guide.md b/skills/release-management/references/section-guide.md deleted file mode 100644 index 7dbb550..0000000 --- a/skills/release-management/references/section-guide.md +++ /dev/null @@ -1,634 +0,0 @@ -# Section Guide - -Detailed guidance for each section of RELEASE.md. - ---- - -## Table of Contents - -1. [Section 1: Project Snapshot](#section-1-project-snapshot) -2. [Section 2: Release Identifiers & Metadata](#section-2-release-identifiers--metadata) -3. [Section 3: Narrative & Positioning](#section-3-narrative--positioning) -4. [Section 4: Artist Background](#section-4-artist-background) -5. [Section 5: Audience & Market Data](#section-5-audience--market-data) -6. [Section 6: DSP & Streaming Strategy](#section-6-dsp--streaming-strategy) -7. [Section 7: Marketing Strategy](#section-7-marketing-strategy) -8. [Section 8: Social & Digital Marketing](#section-8-social--digital-marketing) -9. [Section 9: PR & Media Relations](#section-9-pr--media-relations) -10. [Section 10: Visual & Creative Assets](#section-10-visual--creative-assets) -11. [Section 11: Physical Production](#section-11-physical-production) -12. [Section 12: Merch](#section-12-merch) -13. [Section 13: Experiential & OOH](#section-13-experiential--ooh) -14. [Section 14: Touring & Live](#section-14-touring--live) -15. [Section 15: Team Contacts](#section-15-team-contacts) -16. [Section 16: Budget Overview](#section-16-budget-overview) -17. [Section 17: Performance Tracking](#section-17-performance-tracking) -18. [Section 18: Links & Resources Hub](#section-18-links--resources-hub) - ---- - -## Section 1: Project Snapshot - -**Sharing:** SHAREABLE - -**Purpose:** Quick reference for the core release details. This anchors the entire document. - -### Field Guidance - -| Field | Guidance | -|-------|----------| -| Artist Name | Legal/stage name. Use consistent capitalization. | -| Project Title | Official title. Confirm spelling/stylization with artist. | -| Release Date (Digital) | Always a Friday (industry standard). Format: YYYY-MM-DD | -| Release Date (Physical) | May differ from digital. Often 2-4 weeks later. | -| Announcement Date | When the release is publicly announced. Usually 2-4 weeks before release. | -| Format | Single (1-2 tracks), EP (3-6 tracks), Album (7+ tracks), Deluxe (expanded reissue), Mixtape | -| Territory | WW = Worldwide. Specify if regional exclusives or holdbacks. | -| Primary Genre | Main genre for DSP categorization. Use DSP-recognized genres. | -| Subgenre(s) | Secondary genres. Helps with playlist targeting. | -| Explicit / Clean | Explicit if any track has explicit content. | -| Label | Releasing label. May differ from signing label. | -| Distributor | Who delivers to DSPs (e.g., The Orchard, Stem, TuneCore). | - -### Data Sources - -- Artist/management for dates -- Distributor for UPC assignment confirmation -- A&R for genre classification - -### Red Flags - -- Release date on non-Friday -- Announcement date after release date -- Missing distributor close to release - ---- - -## Section 2: Release Identifiers & Metadata - -**Sharing:** OPS - -**Purpose:** Technical data needed for distribution, royalty tracking, and asset management. - -### 2.1 Digital Identifiers - -| Field | Guidance | -|-------|----------| -| UPC | 12-digit code. Assigned by label/distributor. Never reuse. | -| Spotify URI | Format: `spotify:album:XXXXXXXXX`. Get from Spotify for Artists after delivery. | -| Apple Music ID | Numeric ID from Apple Music for Artists. | -| Amazon ASIN | Amazon Standard Identification Number. | -| YouTube Asset ID | For Content ID claims. | - -### 2.2 Track-Level Metadata - -| Field | Guidance | -|-------|----------| -| Title | Exact title with correct capitalization. Include featured artists in title if applicable. | -| ISRC | 12-character code. Format: CC-XXX-YY-NNNNN. One per track per territory. | -| Duration | Format: M:SS. Get from final master. | -| Explicit? | Yes if explicit language/content. Determines Clean version need. | -| Writers | All songwriters. Full legal names. Order matters for some PROs. | -| Producers | Production credits. May overlap with writers. | -| Features | Featured artists. Format: "feat. Artist Name" | - -### 2.3 Audio Asset Links - -All links should be to cloud storage (Google Drive, Dropbox, etc.) with appropriate sharing permissions. - -**Required formats:** -- Master WAVs: 24-bit/48kHz minimum -- Instrumentals: Same specs as masters -- Stems: If sync licensing anticipated -- Radio Edits: Clean versions for radio - -### Red Flags - -- Missing ISRCs within 2 weeks of release -- Inconsistent writer credits across tracks -- Audio links with expired permissions - ---- - -## Section 3: Narrative & Positioning - -**Sharing:** SHAREABLE - -**Purpose:** The story and positioning that drives all external communication. - -### 3.1 The Pitch (Elevator Version) - -2-3 sentences maximum. Must answer: -- What is this release? -- Why does it matter? -- What makes it unique? - -**Good example:** -> "After a year of silence, Maya Chen returns with 'Midnight Drive,' a synth-pop meditation on grief and renewal. Produced entirely during late-night sessions in her Brooklyn studio, the album transforms personal loss into danceable catharsis." - -**Bad example:** -> "Maya Chen's new album is really good and has 12 tracks." - -### 3.2 Extended Narrative - -500-800 words. Include: -- Context (why now?) -- Creation story -- Themes and meaning -- Artist's perspective (quotes if available) -- Where this fits in career arc - -### 3.3 Project Highlights / Tentpoles - -3-5 bullet points. Each should be newsworthy or pitch-worthy: -- Notable collaborations -- Production details -- Cultural relevance -- Exclusive partnerships -- Tour/performance tie-ins - -### 3.4 Mood & Keywords - -| Category | Purpose | Example | -|----------|---------|---------| -| Mood Tags | DSP playlist categorization | melancholic, uplifting, introspective | -| Vibe Keywords | Marketing/social copy | late-night, nostalgic, euphoric | -| Activity/Use Case | Playlist positioning | workout, study, road trip | - -### 3.5 Comparables - -| Category | Purpose | Guidance | -|----------|---------|----------| -| For Fans Of (RIYL) | Audience targeting | 3-5 artists. Mix of peers and aspirational. | -| Sonic References | Production context | Specific songs/albums that influenced sound. | -| Competitive Positioning | Market context | How this differs from similar releases. | - -### Red Flags - -- Pitch longer than 3 sentences -- No clear "why now" in narrative -- Comparables that don't match genre - ---- - -## Section 4: Artist Background - -**Sharing:** SHAREABLE - -**Purpose:** Artist context for press, DSPs, and partners. - -### 4.1 Artist Bio - -150-200 words. Third person. Should include: -- Who they are (origin, genre, style) -- Key achievements -- Recent/notable releases -- What sets them apart - -Update for each release cycle. - -### 4.2 Sales & Streaming History - -Pull from: -- Spotify for Artists -- Apple Music for Artists -- Chartmetric (if available) -- Label/distributor reports - -Include: -- Monthly listeners (current) -- Total streams (if impressive) -- Certifications (Gold, Platinum) -- Notable editorial playlist history -- High-profile collaborations - -### 4.3 Press & Media Highlights - -Top 5-10 most impressive press hits. Prioritize: -- Major outlets (Pitchfork, Rolling Stone, Billboard) -- Recency -- Quality of coverage (feature vs. roundup) - -### Red Flags - -- Bio longer than 200 words -- Outdated streaming stats -- Press links that are broken - ---- - -## Section 5: Audience & Market Data - -**Sharing:** SHAREABLE - -**Purpose:** Know the audience for targeting and positioning. - -### 5.1 Current Audience Stats - -Update within 1 week of any external share. Include: -- Spotify Monthly Listeners -- All active social handles -- Email/SMS list size (if significant) - -### 5.2 Geographic Data - -Top 5 each for: -- US cities (Spotify for Artists) -- Countries (Spotify for Artists) - -This informs: -- Tour routing -- Geo-targeted ads -- Press priorities - -### 5.3 Demographics - -Age and gender splits from: -- Spotify for Artists -- Instagram Insights -- TikTok Analytics - -### 5.4 Core Fanbase Insights - -Qualitative description. Answer: -- Who are they? -- What else do they listen to? -- What do they care about beyond music? -- How do they discover new music? - -### Red Flags - -- Geographic data without source -- Demographics that don't add to 100% -- Missing TikTok data for artists with TikTok presence - ---- - -## Section 6: DSP & Streaming Strategy - -**Sharing:** SHAREABLE - -**Purpose:** Streaming platform strategy and pitch materials. - -### 6.1 Spotify For Artists Pitch - -**Hard limit: 500 characters.** Every character counts. - -Structure: -1. What's the release? (50 chars) -2. Why it matters (150 chars) -3. Key stats/proof points (150 chars) -4. Genre/mood tags (100 chars) -5. Focus track (50 chars) - -### 6.2 Focus Track(s) - -Choose tracks based on: -- Commercial potential -- Playlist fit -- Streaming-friendly length (2:30-3:30) -- Strong first 30 seconds - -Include clear rationale for each. - -### 6.3 Editorial Playlist Targets - -Research target playlists before filling. Include: -- Specific playlist names -- Why the track fits -- Priority level (High/Medium/Low) - -### 6.4 Pre-Save Strategy - -Pre-save campaigns should launch: -- 2-4 weeks before release -- Immediately after announcement - -Include incentive if offered (exclusive content, merch discount, etc.) - -### Red Flags - -- Spotify pitch over 500 characters -- No focus track rationale -- Pre-save link not live by announcement - ---- - -## Section 7: Marketing Strategy - -**Sharing:** INTERNAL - -**Purpose:** Campaign planning and goal-setting. - -### 7.1 Campaign Goals - -3-5 measurable goals. Use SMART criteria: -- Specific -- Measurable -- Achievable -- Relevant -- Time-bound - -**Good:** "Achieve 500,000 Spotify streams in Week 1" -**Bad:** "Get a lot of streams" - -### 7.2 Key KPIs - -Track both current baseline and target. Include Week 1 targets for: -- Streams (by platform) -- Follower/subscriber growth -- Pre-saves -- Engagement rates - -### 7.3 Rollout Phases - -Standard three phases: - -**Phase 1: Tease & Announce** (2-4 weeks before release) -- Build anticipation -- Drive pre-saves -- Seed content - -**Phase 2: Release Week** -- Maximize day-one impact -- Push for playlist adds -- Content blitz - -**Phase 3: Sustain & Work** (4-8 weeks post-release) -- Extend momentum -- React to performance -- Work radio/press - -### Red Flags - -- No numeric targets -- Phases without specific action items -- Release week phase shorter than 7 days - ---- - -## Section 8: Social & Digital Marketing - -**Sharing:** INTERNAL - -**Purpose:** Organic and paid social strategy. - -### 8.1-8.2 Organic Strategy - -Cover all active platforms. Include: -- Content pillars (3-5 themes) -- Posting frequency by platform -- Key hooks and angles -- TikTok-specific strategy - -### 8.3 Influencer Marketing - -Track all creator partnerships: -- Who they are -- Platform and reach -- Status (pitched, confirmed, posted) -- Rate/budget - -### 8.4 Paid Advertising - -Budget allocation across: -- TikTok Ads -- Meta (Instagram/Facebook) -- YouTube Ads -- Spotify Ad Studio - -Include targeting parameters and flight dates. - -### Red Flags - -- No TikTok strategy for relevant genres -- Paid media launching after release (ideally starts at announcement) -- Influencer list without status updates - ---- - -## Section 9: PR & Media Relations - -**Sharing:** SHAREABLE - -**Purpose:** Press strategy and materials. - -### 9.1 Press Strategy - -Define: -- Main angle (what's the story?) -- Embargo date (if applicable) -- Exclusive offers (premiere, interview) - -### 9.2 Press Targets - -Prioritized media list. Track: -- Outlet and contact -- Priority (Tier 1/2/3) -- Status (pitched, responded, confirmed, published) - -### 9.3 Press Materials - -All links should be: -- Downloadable (not view-only) -- High resolution -- Properly named files - -### Red Flags - -- No press angle defined -- Broken asset links -- Missing hi-res artwork - ---- - -## Section 10: Visual & Creative Assets - -**Sharing:** SHAREABLE - -**Purpose:** Track all visual assets and their status. - -### 10.1 Visual Strategy - -Define the creative direction: -- Core concept -- Color palette -- Aesthetic references -- Mood/vibe - -### 10.2 Asset Inventory - -Track status of each asset: -- In progress -- Delivered -- Approved -- Published - -Required assets for most releases: -- Album artwork (3000x3000 minimum for DSPs) -- Press photos (300dpi for print) -- Spotify Canvas (vertical video) -- Social templates - -### Red Flags - -- Artwork not 1:1 aspect ratio -- Missing Spotify Canvas at release -- No status updates on asset inventory - ---- - -## Section 11: Physical Production - -**Sharing:** OPS / INTERNAL - -**Purpose:** Manufacturing timeline and specs for physical formats. - -### 11.1-11.2 Format Specs - -Detail each format being produced: -- Vinyl: color, weight, packaging type -- CD: jewel case, digipak, special editions -- Cassette: shell color, type - -### 11.3 Manufacturing Timeline - -Standard vinyl timeline is 16-20 weeks. Key milestones: -1. Audio to plant -2. Artwork to plant -3. Test pressing review -4. Manufacturing -5. Shipping to distributor -6. Pre-order launch -7. Street date - -### Red Flags - -- Vinyl timeline under 16 weeks -- No test pressing review scheduled -- Street date before manufacturing complete date - ---- - -## Section 12: Merch - -**Sharing:** INTERNAL - -**Purpose:** Release-related merchandise planning. - -Track each item: -- Description and variants -- Quantities -- Cost and retail price -- Status - -Consider: -- Bundle options with physical/digital -- Exclusive/limited items -- Tour merch alignment - ---- - -## Section 13: Experiential & OOH - -**Sharing:** INTERNAL - -**Purpose:** Events, activations, and out-of-home marketing. - -### 13.1 OOH Activations - -Billboards, wild posting, projections. Include: -- Location specifics -- Flight dates -- Budget -- Proof of completion - -### 13.2-13.3 Events & Partnerships - -Pop-ups, listening events, brand deals. Track status of each. - ---- - -## Section 14: Touring & Live - -**Sharing:** SHAREABLE - -**Purpose:** Tour planning and marketing tie-ins. - -### 14.1 Tour Overview - -Core tour info for coordination. - -### 14.2 Tour Routing - -Full date list. Update status: -- Announced -- On sale -- Sold out -- Cancelled - -### 14.3 Marketing Tie-In - -Align release marketing with tour: -- Geo-target ads to tour markets -- Create city-specific content -- Plan on-site activations - ---- - -## Section 15: Team Contacts - -**Sharing:** INTERNAL - -**Purpose:** Quick reference for all stakeholders. - -Keep updated. Include backup contacts for critical roles. - -**Never share externally** without scrubbing to publicist/management only. - ---- - -## Section 16: Budget Overview - -**Sharing:** INTERNAL - -**Purpose:** Track campaign spending. - -Update weekly during active campaign. Track: -- Allocated (planned budget) -- Spent (actual spend) -- Remaining (buffer) - ---- - -## Section 17: Performance Tracking - -**Sharing:** INTERNAL - -**Purpose:** Measure campaign success and capture learnings. - -### 17.1 Weekly KPI Tracking - -Log metrics weekly. Compare to targets from Section 7.2. - -### 17.2 Wins & Learnings - -Document: -- What worked (for future releases) -- What was adjusted mid-campaign -- Lessons learned - -This section is invaluable for post-mortems and future planning. - ---- - -## Section 18: Links & Resources Hub - -**Purpose:** Central link repository for quick access. - -All links should be: -- Verified working -- Properly permissioned -- Updated when assets move - -This section is typically the last thing updated and first thing referenced. diff --git a/skills/short-video/SKILL.md b/skills/short-video/SKILL.md deleted file mode 100644 index 6ae59b0..0000000 --- a/skills/short-video/SKILL.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -name: short-video -description: End-to-end playbook for producing a vertical, social-ready short-form music video (image + motion + caption + composed final) from an artist and a song. Use when the user asks to make a video, generate content for an artist, create a TikTok or Reel, produce a music-video clip, or kick off the content pipeline — phrases like "make a video about", "generate content for", "create a TikTok", "kick off a video for X", "short-form video", or "music video for $ARTIST". The skill has two paths: an async pipeline (`POST /api/content/create` + poll `/api/tasks/runs`) for short-shell agents (preferred), and a manual five-step recipe in `references/short-video-manual.md` when you want to swap a single stage. ---- - -# Short Video - -The canonical recipe used internally by Recoup's `create-content` background task. Produces a 9:16 music-video clip from an existing artist + song, ready to post. - -## Two paths - -| Path | When to use | Where it lives | -|---|---|---| -| **Async pipeline** | Default for agents — fires server-side and polls. Survives short shell timeouts. | This file (below). | -| **Manual walkthrough** | Humans or long-lived shells that want explicit control over a single step (different caption length, different motion, different audio). | `references/short-video-manual.md` | - -For sourcing the underlying `song.mp3`, see `references/song-sourcing.md` — it covers the sandbox-repo → YouTube → user-supplied fallback chain. **Do not silently default to a placeholder track.** - -## Async pipeline (preferred for agents) - -`POST /api/content/video` is synchronous and routinely takes 60–180s. Most agent shells (Claude Cowork, OpenAI tool calls, etc.) cap a single command at 30–60s and kill background processes when the shell exits. The manual walkthrough is effectively un-runnable from those environments. Use the async path instead — same five steps, run server-side. - -```bash -# Trigger -RUN_IDS=$(curl -sS -X POST "https://api.recoupable.com/api/content/create" \ - -H "x-api-key: $RECOUP_API_KEY" -H "Content-Type: application/json" \ - -d "$(jq -n --arg artist "$ARTIST_ACCOUNT_ID" --arg template "$TEMPLATE" \ - '{artist_account_id: $artist, template: $template}')" \ - | jq -r '.runIds[]') - -# Poll (every ~10s) until COMPLETED / FAILED / CANCELED / CRASHED -RUN_ID=$(echo "$RUN_IDS" | head -1) -until STATUS=$(curl -sS "https://api.recoupable.com/api/tasks/runs?runId=$RUN_ID" \ - -H "x-api-key: $RECOUP_API_KEY" \ - | jq -r '.runs[0].status') && \ - [[ "$STATUS" =~ ^(COMPLETED|FAILED|CANCELED|CRASHED)$ ]]; do - sleep 10 -done - -# Read the output -curl -sS "https://api.recoupable.com/api/tasks/runs?runId=$RUN_ID" \ - -H "x-api-key: $RECOUP_API_KEY" \ - | jq '.runs[0].output' -# -> { videoSourceUrl, imageUrl, captionText, template, lipsync, audio: {...} } -``` - -See [Tasks Runs](https://developers.recoupable.com/api-reference/tasks/runs) for the full status enum and the `CreateContentRunOutput` schema. - -### Resolving `$ARTIST_ACCOUNT_ID` - -`POST /api/content/create` needs the artist's `account_id`. Three calls: - -```bash -ORG_ID=$(curl -sS "https://api.recoupable.com/api/organizations" \ - -H "x-api-key: $RECOUP_API_KEY" | jq -r '.organizations[0].id') - -ARTIST_ACCOUNT_ID=$(curl -sS "https://api.recoupable.com/api/artists?org_id=$ORG_ID" \ - -H "x-api-key: $RECOUP_API_KEY" \ - | jq -r --arg name "$ARTIST_NAME" '.artists[] | select(.name == $name) | .account_id') -``` - -The artist record exposes both `id` and `account_id` (both UUIDs). Use **`account_id`** — `id` is the artist row's primary key, `account_id` is the underlying account that owns it. The two are easy to swap; you'll get a 404 from `/api/content/create` if you pass the wrong one. - -## When the agent should reach for the manual walkthrough - -The async pipeline produces a finished video with sensible defaults. Switch to `references/short-video-manual.md` when: - -- The user wants a **different caption length** than the template default. -- The user supplies their own **reference image** and wants to skip generation. -- The user wants to **swap the audio** with a specific clip rather than the song from the sandbox. -- The user wants to **inspect intermediate outputs** (the raw image, the un-composed 16:9 clip) before composition. - -The manual walkthrough is the same five steps, run individually. It is significantly slower in short shells — use it only when the async pipeline's defaults don't fit. diff --git a/skills/short-video/references/short-video-manual.md b/skills/short-video/references/short-video-manual.md deleted file mode 100644 index c106c8f..0000000 --- a/skills/short-video/references/short-video-manual.md +++ /dev/null @@ -1,185 +0,0 @@ -# Short video — manual walkthrough - -The same five-step recipe as the async pipeline, but run by hand or step by step when you want to swap a single stage (different prompt, different motion, different caption length, different audio). - -**When to use this instead of the async pipeline:** humans operating the API directly, or agents in a long-lived shell that wants explicit control over each stage. Most agent shells should use the async path documented in `skills/short-video/SKILL.md` instead — `POST /api/content/video` routinely takes 60–180 seconds and times out in short shells. - -## Prerequisites - -- An auth credential for `api.recoupable.com`. Two options. Pick one and use it for every call below: - - **API key** (`recoup_sk_…`, recommended for sandbox / agent use): pass as `-H "x-api-key: $RECOUP_API_KEY"`. - - One-shot agent: `POST /api/agents/signup` with an `agent+{unique}@recoupable.com` email returns the key immediately. - - Real-email signup: same endpoint with a real email mails a 6-digit code; complete with `POST /api/agents/verify`. See [Agents](https://developers.recoupable.com/agents). - - **Privy access token** (for end-user flows in chat/UI): pass as `-H "Authorization: Bearer $RECOUP_ACCESS_TOKEN"`. - - The examples below use `x-api-key`. Substitute `Authorization: Bearer …` if you're using a Privy token. -- `$ARTIST_NAME`, `$SONG_TITLE`, `$SONG_LYRICS_CLIP` (a 1–2 sentence mood snippet) -- `$REFERENCE_IMAGE_URL` *(optional)*: an artist photo or album cover to seed the image. If your template's purpose is "show this exact image" (e.g. `album-record-store`), set this and skip image generation in step 2. -- A `song.mp3` for step 5. **Don't ask the user for a local file.** See `references/song-sourcing.md` for the fallback chain. -- `ffmpeg` installed locally for step 5. - -## Step 0: Scaffold the workspace BEFORE any API call - -The `VIDEO.md` checklist *is* the workflow state. Tick boxes and persist values back to the frontmatter as you go. To resume later, find the first unchecked box. - -```bash -VIDEO_SLUG=$(echo "$SONG_TITLE" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-//; s/-$//') -VIDEO_DIR="videos/$VIDEO_SLUG" -mkdir -p "$VIDEO_DIR" - -cat > "$VIDEO_DIR/VIDEO.md" < "$VIDEO_DIR/caption.txt" - -# Single-line filter graph: newlines inside -filter_complex are literal characters and break the [v] label -ffmpeg -y \ - -stream_loop -1 -i "$VIDEO_DIR/clip.mp4" \ - -i "$SONG_PATH" \ - -filter_complex "[0:v]crop=ih*9/16:ih,scale=1080:1920,drawtext=fontfile=$CAPTION_FONT:textfile=$VIDEO_DIR/caption.txt:fontcolor=$CAPTION_COLOR:fontsize=$CAPTION_FONT_SIZE:x=(w-text_w)/2:y=h-text_h-120:box=1:boxcolor=$CAPTION_STROKE@0.5:boxborderw=20[v]" \ - -map "[v]" -map "1:a" \ - -shortest -c:v libx264 -c:a aac -pix_fmt yuv420p \ - "$FINAL_PATH" -``` - -**After:** write `finalVideoPath`, tick the box. With every box ticked, the music video is complete. - -The checklist is the source of truth. If a box isn't ticked, treat the step as not run. diff --git a/skills/short-video/references/song-sourcing.md b/skills/short-video/references/song-sourcing.md deleted file mode 100644 index a56e45c..0000000 --- a/skills/short-video/references/song-sourcing.md +++ /dev/null @@ -1,34 +0,0 @@ -# Song sourcing fallback chain - -The short-video skill's compose step (and the async pipeline's lipsync mode) need a `song.mp3`. Don't assume one exists locally, and don't assume the user has one to hand. - -## Order of attempts - -### 1. Check the artist's sandbox repo first - -Each Recoup account has a backing GitHub repo. If the user has imported songs through Recoup, they live at predictable paths: - -``` -.openclaw/workspace/orgs/{org-slug}/artists/{artist-slug}/songs/{song-slug}/{song-slug}.mp3 - /lyrics.json - /clips.json -``` - -Discover the repo with `GET /api/sandboxes` (returns `github_repo` and a `filetree`). Fetch a file with `GET /api/sandboxes/file?path=…`. - -**Binary files (`.mp3`, `.png`, `.mp4`) come back base64-encoded in the `content` field. Decode before writing to disk.** Skipping this step produces a file that looks the right size but isn't playable, which fails silently in ffmpeg. - -### 2. If no song is in the sandbox, ask the user how to proceed - -Two options to offer. **Don't pick silently — the cost of pulling the wrong song from YouTube is high enough that the user should make the call.** - -- *"Want me to fetch the audio from YouTube?"* The agent downloads via `yt-dlp` (or equivalent) and saves locally. The user is responsible for any rights / DSP-licensing implications. -- *"Want to supply the song yourself?"* The user uploads or supplies a path; the agent reads from there. - -### 3. Do not fall back to "use a placeholder track" - -A music video without the song is not a deliverable. If sourcing fails through both paths above, stop and surface the blocker rather than producing a video with the wrong audio. - -## Why this matters - -The audio is the only stage where the agent can produce something that *looks* correct (right runtime, right format, right resolution) while being substantively wrong. Visual stages either render or 422; audio stages render fine with the wrong content. That's why the fallback chain is opinionated and surfaces failure rather than fabricating. diff --git a/skills/skill-pack-benchmark/SKILL.md b/skills/skill-pack-benchmark/SKILL.md deleted file mode 100644 index b7357d0..0000000 --- a/skills/skill-pack-benchmark/SKILL.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -name: skill-pack-benchmark -description: >- - Benchmark ANY plugin, skill pack, or skills folder against the current - frontier of skill/plugin design (gstack, gbrain, compound-engineering, PM OS). - Use whenever someone wants to grade, audit, review, or compare a skills - directory or plugin and asks things like "is this skill pack good?", "how does - our plugin compare to the frontier?", "are these skills fat enough?", "should - these skills be combined or split?", "what should we steal from gstack?", - "benchmark this plugin", "rate my skills folder", or "audit this skill bundle". - Works on any domain (engineering, music, finance, bio — anything) and any - harness layout. It measures the pack deterministically (skill count, SKILL.md - body sizes, tests/evals/resolver/hooks/learnings signals, catalog token - budget, description-overlap risks), grades it on a 15-dimension scorecard, then - produces a fat-enough / combine / split / parameterize diagnosis and a - prioritized list of frontier moves to steal — each with its boundary. Do NOT - use this to author a single new skill (use a skill-creator) or to run a pack's - own tests. ---- - -# Skill Pack Benchmark - -Grade any plugin or skills folder against the frontier bar for what makes skills -and plugins *good*. The output is an evidence-backed report: where the pack -sits, what's fat vs. thin, what should be combined/split/parameterized, and the -prioritized, boundary-carrying moves worth stealing. - -This skill follows the very discipline it grades: a **deterministic** measurement -script does the counting (so numbers are never eyeballed), and the **latent** -judgment — scoring, diagnosis, recommendations — is the agent's job, grounded in -the reference standard. The script and references ship alongside this `SKILL.md`; -invoke them with the relative paths below. - -## Inputs - -- **Target** (required): a path to a plugin directory, a folder of skills, or a - whole repo. The skill auto-discovers every `SKILL.md` underneath. -- **Goal of the pack** (infer or ask once): is it a vertical *"everything-in-a-box"* - bundle, a focused *capability plugin*, or a *teaching demonstration*? The bar - differs — grade against intent, not a single absolute. - -If no target is given, ask for the path before doing anything else. - -## The procedure (five phases) - -### Phase 1 — Frame: what is this pack trying to be? - -Before measuring, establish intent (one short paragraph). Read the pack's README -/ top-level manifest if present, and skim 2–3 skill descriptions. Decide which -archetype it is — it sets which scorecard dimensions matter most: - -- **Vertical "in-a-box" bundle** (PM OS analog): expect tiering (system / - workflow-orchestrator / reusable-leaf) and, past ~40 skills, tested routing. -- **Focused capability plugin**: expect a router + a handful of real leaves; - routing tests are overkill below ~40 skills. -- **Teaching / demonstration pack**: thin and testless is *correct* — say so and - grade gently. - -### Phase 2 — Measure (deterministic; never guess the numbers) - -Run the bundled measurement script against the target: - -```bash -python3 scripts/measure.py -# JSON for your own downstream use: -python3 scripts/measure.py --json -o /tmp/bench.json -``` - -`scripts/measure.py` ships alongside this skill (stdlib-only, no network). It -reports: skill count; `SKILL.md` **body** line min/median/max + size bands -(tiny/leaf/deep/fat); per-skill footprint (body + co-located refs + scripts); -the always-loaded **catalog** token estimate vs the ~7K budget; the pack-level -**signals** that the frontier treats as the *defining properties* of a skill -pack (deterministic scripts, unit/integration tests, LLM evals, resolver/routing -tests, reachability gate, hooks, learnings store, distribution manifests); and -**description-overlap candidates** (pairs most likely to mis-route). - -Take the numbers as given — your job is to interpret them, not re-derive them. - -### Phase 3 — Grade against the 15-dimension scorecard - -Read `references/scorecard.md` and fill one row per dimension, grading each -**✅ at/ahead of frontier · ⚠️ present-but-weak · ❌ gap**, with the measured -evidence and a one-line so-what. To grade the judgment-depth dimensions (1, 5, -9, 12) you must **read actual files**, not just the counts: open the 2–3 fattest -and 2–3 thinnest `SKILL.md` bodies and judge whether the lines are *encoded -judgment* (multi-phase procedure, interaction contract, verification loop) or -boilerplate/padding. The bar and the real frontier numbers are in -`references/frontier-benchmark.md` — cite it. - -A signal the script can't see: when grading the **deterministic substrate** (dim -3), spot-read a script or two and ask the Foxconn-factory question — *is this -exact work (math, parsing, validation) or code written to distrust the model?* -Only the former earns its keep (see `references/frontier-benchmark.md` §7). - -### Phase 4 — Diagnose: fat enough? combine? split? parameterize? - -For each plugin or skill group, classify it **Hold / Fatten / Parameterize-or- -combine / Split** using the framework in `references/scorecard.md`. Anchor every -call in evidence: - -- Use the **size bands** to spot thin skills doing deep jobs (→ Fatten). -- Use the **overlap candidates** as the shortlist for Parameterize-or-combine, - but only recommend combining when the *process is the same* and only the - input/depth varies — otherwise keep them split (the god-skill anti-pattern). -- Flag the **~40-skill threshold** explicitly if the pack is at/over it with no - resolver eval or reachability audit. - -### Phase 5 — Report: prioritized moves with boundaries - -Produce the final report (structure below). Pull recommendations from the -prioritized "what to steal" list in `references/scorecard.md` (P1–P7), and -**carry each boundary** — when NOT to do the move. Always include a "keep what -you already lead on" section so a later refactor doesn't delete a strength. - -## Output format - -Deliver a single report with these sections: - -1. **Verdict in one screen** — archetype, the headline gap, and the single - highest-leverage move, in ~5 sentences. -2. **The numbers** — a compact table from `measure.py` (skills, body - min/median/max, bands, catalog tokens, signals). -3. **Scorecard** — the 15-row table, graded, with evidence. -4. **Diagnosis** — per plugin/group: Hold / Fatten / Combine / Split, with the - reason. -5. **Prioritized moves** — the P1–P7 steal-list filtered to what this pack - actually needs, each with its boundary. -6. **Keep (don't regress)** — the dimensions the pack already leads on. - -Lead with what's **measured**; present judgment calls as judgment. Never inflate -a grade the evidence doesn't support — an unbacked benchmark is worse than none. - -## Boundaries (when this skill does NOT apply) - -- **Authoring one skill** → use a skill-creator, not this. -- **Running a pack's own test suite** → that's the pack's CI, not a benchmark. -- **Grading a deliberate demonstration pack as if it were production** → don't; - thin + testless is the *correct* shape for teaching the format. -- **Cargo-culting frontier features** → every recommendation must carry its - boundary. Fat ≠ verbose; tests ≠ a Foxconn factory; an explicit resolver below - ~40 skills is overhead, not progress. - -## What to read - -- `references/frontier-benchmark.md` — the standard: the frontier packs and - their real numbers, the core patterns (thin-harness/fat-skills, latent vs. - deterministic, skill-as-method-call, the tested bundle, resolver routing, the - complexity ratchet + Foxconn counter-weight), the anatomy of fat with verified - line counts, and the size-budget discipline. **Read before grading.** -- `references/scorecard.md` — the 15-dimension rubric, the - Hold/Fatten/Combine/Split diagnosis framework, and the prioritized P1–P7 steal - list with boundaries. -- `scripts/measure.py` — the deterministic measurement tool (ships with this - skill; run it, don't reimplement it). diff --git a/skills/skill-pack-benchmark/references/scorecard.md b/skills/skill-pack-benchmark/references/scorecard.md deleted file mode 100644 index 13295ca..0000000 --- a/skills/skill-pack-benchmark/references/scorecard.md +++ /dev/null @@ -1,79 +0,0 @@ -# The benchmark scorecard — 15 dimensions to grade any skill pack - -Fill one row per dimension for the pack under review. Grade each -**✅ at/ahead of frontier · ⚠️ present but weak/untested · ❌ gap**, cite the -**measured evidence** (from `scripts/measure.py` or a file path), and give a -one-line **so-what**. The frontier column is the bar from -`references/frontier-benchmark.md`. Do not invent a numeric score; the three-way -grade + evidence is the deliverable. - -> A pack does **not** need ✅ on all 15. Demonstration packs *should* be thin and -> untestless on purpose. Grade against what the pack is *trying to be* (vertical -> "in-a-box" bundle vs. focused capability plugin vs. teaching demo), which you -> establish in Phase 1 of the SKILL.md procedure. - -| # | Dimension | Frontier bar | How to measure it | Grade | -|---|---|---|---|---| -| 1 | **`SKILL.md` body depth** | 800–3,000 lines for substantive skills; deep encoded judgment + interaction contracts | `measure.py` body min/median/max + bands; read the 2–3 fattest + 2–3 thinnest bodies | | -| 2 | **Total skill footprint** | large across the board (body + refs + scripts) | `measure.py` per-skill total column | | -| 3 | **Deterministic substrate** | many narrow CLIs (gstack 62+); converters; zero-LLM engines | `measure.py` "deterministic substrate" count; spot-read 1–2 scripts | | -| 4 | **Unit / integration tests** | gstack ~1,100+; CE ~1,094 | `measure.py` "unit/integration tests" signal | | -| 5 | **LLM-as-judge evals** | gstack `skill-llm-eval`; gbrain BrainBench | `measure.py` "LLM evals" signal; confirm it judges *quality*, not just existence | | -| 6 | **Resolver evals (does the right skill *fire*?)** | gstack 3 + gbrain 15 fixtures w/ adversarial negatives | `measure.py` "resolver/routing tests"; the rarest, highest-value test | | -| 7 | **Reachability audit (`check-resolvable`)** | gbrain ships it as a build gate | `measure.py` "reachability gate" signal | | -| 8 | **Routing / resolver design** | explicit `RESOLVER.md` (>40 skills) or tested description-match (<40) | skill count vs ~40 threshold; read a few `description:` fields for trigger phrases | | -| 9 | **Completion gating** | externally-verified "it's ready" gate beats self-attestation | look for `Stop`/completion hooks, validators that return `ok` | | -| 10 | **Immutable-source / anti-cheat** | PreToolUse hook blocking writes to evidence/source | read `hooks.json` if present | | -| 11 | **Compounding learnings store** | CE `docs/solutions/` + dedup; gstack `learnings.jsonl` | `measure.py` "learnings store" signal | | -| 12 | **Parameterization (method-call)** | `/qa` tiers, `/goal` signature | look for depth/mode args vs. near-duplicate skills (see overlap candidates) | | -| 13 | **Multi-surface distribution** | author once → ~11 harnesses; per-harness manifests | `measure.py` "multi-surface distribution" manifests | | -| 14 | **Always-loaded catalog budget** | ≤ ~7,000 tokens of descriptions | `measure.py` catalog est_tokens vs 7K | | -| 15 | **Progressive disclosure** | carve `sections/`/`references/`; size-budget ratchet | refs present? bodies lazy vs. everything in one file? | | - -## Turning grades into a diagnosis (per plugin / sub-pack) - -After grading, classify each skill (or group) with the **fat-enough? combine? -split?** framework from the benchmark methodology: - -- **Hold** — fat enough and correctly scoped. Don't touch. -- **Fatten** — thin body for a deep job: encode the real multi-phase procedure + - interaction contract + verification loop (anatomy in `frontier-benchmark.md` - §9). Boundary: fat ≠ verbose; push depth into `references/`, keep the body a - tight high-judgment procedure. -- **Parameterize / combine** — near-duplicate skills running the *same* process - on different inputs → collapse into one mode-driven skill (use the - `measure.py` overlap candidates as the shortlist). Boundary: only when the - *process is the same*; if two "modes" run genuinely different steps, keep them - split (the god-skill anti-pattern). -- **Split** — one filename running different steps per argument → break apart. - -## The prioritized "what to steal" moves (apply only what the grade calls for) - -Ordered by typical leverage. Each carries its boundary — carrying the boundary -is what keeps a benchmark from cargo-culting. - -- **P1 — Ship the tested-bundle layer** (dims 4–7). Add resolver evals - (`{intent, expected_skill, must_not_fire}` with adversarial negatives) and - LLM-as-judge evals for judgment-heavy outputs. Keep them affordable: cheap - deterministic checks gate every change, expensive LLM evals run periodically. - *Boundary:* not on demonstration/throwaway skills; don't build evals before a - skill's shape stabilizes (it's a *graduation* step), and don't let the suite - become a Foxconn factory. -- **P2 — Add a reachability + routing layer** (dims 7–8) *only if the pack is - at/over ~40 skills*. A CI check that every skill is reachable (no "dark" - skills) and overlapping descriptions are MECE. *Boundary:* below ~40 skills - the implicit description-matcher is correct and an explicit `RESOLVER.md` is - overhead. -- **P3 — Fatten the thin capability skills** (dims 1–2). Encode the actual - procedure + interaction contract + verification loop, not a 50-line API - wrapper. *Boundary:* fat ≠ verbose; bound it (the size-budget discipline). -- **P4 — Parameterize instead of proliferate** (dim 12). Collapse near-duplicate - skills into fewer, fatter, mode-driven ones. *Boundary:* only when the process - is identical and only input/depth varies. -- **P5 — Add a compounding learnings store** (dim 11) for *recurring* workflows. - *Boundary:* not for one-off jobs; keep dedup discipline or the store rots. -- **P6 — Promote session-start from passive to imperative** (one directive per - session; passive walls of context get ignored). -- **P7 — Keep what the pack already leads on** (don't regress completion gates, - anti-cheat hooks, distribution hygiene). Name these explicitly so a later - refactor doesn't delete a strength. diff --git a/skills/skill-pack-benchmark/scripts/measure.py b/skills/skill-pack-benchmark/scripts/measure.py deleted file mode 100644 index 1d247ac..0000000 --- a/skills/skill-pack-benchmark/scripts/measure.py +++ /dev/null @@ -1,350 +0,0 @@ -#!/usr/bin/env python3 -"""Measure any skill pack (plugin or skills folder) against the frontier bar. - -This is the *deterministic* half of the skill-pack-benchmark skill: it counts -what can be counted exactly so the agent never eyeballs (and mis-guesses) the -numbers. It is harness-agnostic and reads only the target you point it at — it -makes no network calls and writes nothing except its report. - -What it measures (per the fat-skills benchmark methodology): - * skill count + SKILL.md *body* line distribution (min / median / max + bands) - * per-skill footprint = body + co-located references + co-located scripts - * the always-loaded "catalog" cost (sum of frontmatter descriptions, ~tokens) - * pack-level signals the frontier treats as the *definition* of a skill pack: - - deterministic substrate (scripts), unit/integration tests, LLM evals, - resolver / routing tests, reachability gate, hooks, learnings store, - multi-surface distribution manifests - * candidate description overlaps (where implicit routing may mis-fire) - -Usage: - python3 scripts/measure.py # human-readable report - python3 scripts/measure.py --json # machine-readable JSON - python3 scripts/measure.py --json -o m.json # JSON to a file - - may be a single plugin dir, a folder of skills, or a whole repo. -Standard library only; works on Python 3.8+. -""" -from __future__ import annotations - -import argparse -import json -import re -import statistics -import sys -from pathlib import Path - -# ── File-type buckets ──────────────────────────────────────────────────────── -CODE_EXT = {".py", ".ts", ".tsx", ".js", ".mjs", ".cjs", ".sh", ".bash", ".rb", ".go", ".rs"} -DOC_EXT = {".md", ".mdx", ".txt", ".rst"} - -# A SKILL.md body line band → role hint (see references/frontier-benchmark.md §"how fat is fat"). -BANDS = [ - ("tiny", 0, 99), # utility / toggle / demonstration - ("leaf", 100, 399), # reusable single-task leaf (references-backed) - ("deep", 400, 799), # approaching a deep capability skill - ("fat", 800, 10**9), # frontier-grade deep capability / orchestrator body -] - -# Pack-level signal detectors. Each maps a label → (filename/dirname regex, -# whether it matches directory names too). Matched case-insensitively against -# the path's parts and final name. -TEST_RE = re.compile(r"(\.test\.[a-z]+$)|(^test_.*\.py$)|(_test\.py$)|(\.spec\.[a-z]+$)", re.I) -EVAL_RE = re.compile(r"eval", re.I) -# A *routing test/table* — not just any file with "resolver" in the name (a -# reference doc like `account-resolver.md` is not a routing test). -RESOLVER_FILE_RE = re.compile( - r"^RESOLVER\.md$|routing[-_]?eval|routing.*\.jsonl|resolver.*(\.test\.|\.spec\.|eval)|check[-_]resolvable", - re.I, -) -REACHABLE_RE = re.compile(r"check[-_]resolvable|reachab", re.I) -LEARNINGS_RE = re.compile(r"learnings|solutions", re.I) -HOOKS_RE = re.compile(r"^hooks\.json$", re.I) -MANIFEST_DIRS = {".claude-plugin", ".cursor-plugin", ".codex-plugin", ".agents"} - -# Directories never worth walking into. -SKIP_DIRS = {".git", "node_modules", "__pycache__", ".venv", "venv", "dist", "build", ".next"} - - -def _count_lines(path: Path) -> int: - try: - return len(path.read_text(encoding="utf-8", errors="replace").splitlines()) - except OSError: - return 0 - - -def _walk_files(root: Path): - """Yield every file under root, skipping noise directories.""" - for p in root.rglob("*"): - if p.is_dir(): - continue - if any(part in SKIP_DIRS for part in p.parts): - continue - yield p - - -def parse_frontmatter(text: str): - """Return (frontmatter_dict_lite, body_text). Only name + description matter. - - Hand-rolled (no PyYAML) so the script stays stdlib-only. Handles the two - common shapes: inline `description: ...` and folded `description: >-`. - """ - lines = text.splitlines() - if not lines or lines[0].strip() != "---": - return {}, text - end = None - for i in range(1, len(lines)): - if lines[i].strip() == "---": - end = i - break - if end is None: - return {}, text - fm_lines = lines[1:end] - body = "\n".join(lines[end + 1:]) - - name = "" - description = "" - i = 0 - while i < len(fm_lines): - line = fm_lines[i] - m = re.match(r"^(name|description)\s*:\s*(.*)$", line) - if not m: - i += 1 - continue - key, val = m.group(1), m.group(2).strip() - if val in (">", ">-", "|", "|-", ""): - # Folded/literal block: gather subsequent more-indented lines. - block = [] - base_indent = len(line) - len(line.lstrip()) - j = i + 1 - while j < len(fm_lines): - nxt = fm_lines[j] - if nxt.strip() == "": - block.append("") - j += 1 - continue - indent = len(nxt) - len(nxt.lstrip()) - if indent <= base_indent: - break - block.append(nxt.strip()) - j += 1 - val = " ".join(s for s in block if s != "").strip() - i = j - else: - val = val.strip().strip('"').strip("'") - i += 1 - if key == "name": - name = val - else: - description = val - return {"name": name, "description": description}, body - - -def measure_skill(skill_md: Path): - text = skill_md.read_text(encoding="utf-8", errors="replace") - fm, body = parse_frontmatter(text) - body_lines = len(body.splitlines()) - skill_dir = skill_md.parent - - ref_lines = ref_files = 0 - script_lines = script_files = 0 - for sub, exts, kind in (("references", DOC_EXT, "ref"), ("scripts", CODE_EXT, "script")): - d = skill_dir / sub - if not d.is_dir(): - continue - for f in d.rglob("*"): - if f.is_file() and f.suffix.lower() in exts: - n = _count_lines(f) - if kind == "ref": - ref_lines += n - ref_files += 1 - else: - script_lines += n - script_files += 1 - - name = fm.get("name") or skill_dir.name - desc = fm.get("description", "") - band = next(b for b, lo, hi in BANDS if lo <= body_lines <= hi) - return { - "name": name, - "dir": str(skill_dir), - "body_lines": body_lines, - "band": band, - "ref_lines": ref_lines, - "ref_files": ref_files, - "script_lines": script_lines, - "script_files": script_files, - "total_lines": body_lines + ref_lines + script_lines, - "description": desc, - "description_chars": len(desc), - "has_triggers": bool(re.search(r"\b(use when|trigger|when the user|proactively)\b", desc, re.I)), - } - - -def detect_signals(root: Path): - """Scan the whole target tree for pack-level signals (defining properties).""" - sig = { - "test_files": [], "eval_files": [], "resolver_files": [], - "reachability_files": [], "learnings_paths": [], "hooks_files": [], - "distribution_manifests": [], "deterministic_scripts": 0, - } - seen_manifest_dirs = set() - for p in _walk_files(root): - rel = str(p.relative_to(root)) - name = p.name - if TEST_RE.search(name): - sig["test_files"].append(rel) - if EVAL_RE.search(name) and p.suffix.lower() in (CODE_EXT | {".jsonl", ".json"}): - sig["eval_files"].append(rel) - if RESOLVER_FILE_RE.search(name): - sig["resolver_files"].append(rel) - if REACHABLE_RE.search(name): - sig["reachability_files"].append(rel) - if HOOKS_RE.search(name): - sig["hooks_files"].append(rel) - if p.suffix.lower() in CODE_EXT and not TEST_RE.search(name) and "/scripts/" in f"/{rel}": - sig["deterministic_scripts"] += 1 - # learnings store: a docs/solutions dir or a learnings.* file - if LEARNINGS_RE.search(rel) and ("solutions" in rel.lower() or "learnings" in name.lower()): - top = rel.split("/")[0:3] - sig["learnings_paths"].append("/".join(top)) - # distribution manifests - for part in p.parts: - if part in MANIFEST_DIRS and part not in seen_manifest_dirs: - seen_manifest_dirs.add(part) - sig["distribution_manifests"].append(part) - - # de-dup + cap noisy lists for readability - sig["learnings_paths"] = sorted(set(sig["learnings_paths"])) - sig["distribution_manifests"] = sorted(set(sig["distribution_manifests"])) - return sig - - -_WORD_RE = re.compile(r"[a-z0-9']+") -_STOP = set("the a an and or of to for with use when this that is are be it as on in by your you i we our skill skills user".split()) - - -def _desc_words(desc: str): - return {w for w in _WORD_RE.findall(desc.lower()) if w not in _STOP and len(w) > 2} - - -def overlap_pairs(skills, threshold=0.45): - """Flag skill description pairs with high word-overlap (Jaccard) — places - where the implicit description-resolver is most likely to mis-fire.""" - pairs = [] - sets = [(s["name"], _desc_words(s["description"])) for s in skills] - for i in range(len(sets)): - for j in range(i + 1, len(sets)): - a, sa = sets[i] - b, sb = sets[j] - if not sa or not sb: - continue - inter = len(sa & sb) - union = len(sa | sb) - jac = inter / union if union else 0.0 - if jac >= threshold: - pairs.append({"a": a, "b": b, "jaccard": round(jac, 2)}) - return sorted(pairs, key=lambda x: -x["jaccard"]) - - -def build_report(root: Path): - skill_files = sorted(root.rglob("SKILL.md")) - skill_files = [s for s in skill_files if not any(part in SKIP_DIRS for part in s.parts)] - skills = [measure_skill(s) for s in skill_files] - bodies = [s["body_lines"] for s in skills] or [0] - band_counts = {b: 0 for b, _, _ in BANDS} - for s in skills: - band_counts[s["band"]] += 1 - catalog_chars = sum(s["description_chars"] for s in skills) - return { - "target": str(root), - "skill_count": len(skills), - "body_lines": { - "min": min(bodies), - "median": int(statistics.median(bodies)), - "max": max(bodies), - }, - "bands": band_counts, - "catalog": { - "description_chars": catalog_chars, - "est_tokens": round(catalog_chars / 4), - "budget_tokens": 7000, - "over_budget": (catalog_chars / 4) > 7000, - }, - "signals": detect_signals(root), - "overlap_candidates": overlap_pairs(skills), - "skills": sorted(skills, key=lambda s: -s["total_lines"]), - } - - -def _has(lst): - return f"YES ({len(lst)})" if lst else "— none" - - -def print_report(r): - out = print - out(f"\nSKILL PACK BENCHMARK — {r['target']}") - out("=" * 72) - out(f"Skills: {r['skill_count']} " - f"SKILL.md body lines min/median/max = " - f"{r['body_lines']['min']}/{r['body_lines']['median']}/{r['body_lines']['max']}") - b = r["bands"] - out(f"Body-size bands: tiny<100={b['tiny']} leaf 100-399={b['leaf']} " - f"deep 400-799={b['deep']} fat 800+={b['fat']}") - c = r["catalog"] - flag = " ⚠ OVER ~7K BUDGET" if c["over_budget"] else " (within ~7K budget)" - out(f"Always-loaded catalog (descriptions): ~{c['est_tokens']} tokens{flag}") - - out("\nPack-level signals (the frontier's defining properties of a skill pack):") - s = r["signals"] - out(f" deterministic substrate (scripts) : {s['deterministic_scripts']} script file(s)") - out(f" unit / integration tests : {_has(s['test_files'])}") - out(f" LLM-as-judge evals : {_has(s['eval_files'])}") - out(f" resolver / routing tests : {_has(s['resolver_files'])}") - out(f" reachability gate (check-resolvable): {_has(s['reachability_files'])}") - out(f" hooks layer : {_has(s['hooks_files'])}") - out(f" compounding learnings store : {_has(s['learnings_paths'])}") - out(f" multi-surface distribution : {', '.join(s['distribution_manifests']) or '— none'}") - - if r["overlap_candidates"]: - out("\nDescription-overlap candidates (implicit routing may mis-fire — test these):") - for p in r["overlap_candidates"][:12]: - out(f" {p['jaccard']:>4} {p['a']} ⇄ {p['b']}") - - out("\nPer-skill footprint (body + co-located refs + scripts), fattest first:") - out(f" {'skill':<34}{'body':>6}{'refs':>7}{'scripts':>9}{'total':>8} band") - for sk in r["skills"]: - out(f" {sk['name'][:33]:<34}{sk['body_lines']:>6}{sk['ref_lines']:>7}" - f"{sk['script_lines']:>9}{sk['total_lines']:>8} {sk['band']}") - out("\nNext: score these against references/scorecard.md and read" - " references/frontier-benchmark.md for the bar + what to steal.\n") - - -def main(argv): - ap = argparse.ArgumentParser(description="Measure a skill pack against the frontier bar.") - ap.add_argument("path", help="plugin dir, skills folder, or repo root to measure") - ap.add_argument("--json", action="store_true", help="emit JSON instead of a human report") - ap.add_argument("-o", "--out", help="write JSON to this file (implies --json)") - args = ap.parse_args(argv) - - root = Path(args.path).expanduser().resolve() - if not root.is_dir(): - print(f"error: not a directory: {root}", file=sys.stderr) - return 2 - - report = build_report(root) - if report["skill_count"] == 0: - print(f"No SKILL.md found under {root}", file=sys.stderr) - return 1 - - if args.out: - Path(args.out).write_text(json.dumps(report, indent=2), encoding="utf-8") - print(f"wrote {args.out}") - elif args.json: - print(json.dumps(report, indent=2)) - else: - print_report(report) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main(sys.argv[1:])) diff --git a/skills/social-posts/SKILL.md b/skills/social-posts/SKILL.md deleted file mode 100644 index c22c722..0000000 --- a/skills/social-posts/SKILL.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -name: social-posts -description: Write and ship data-grounded LinkedIn and X (Twitter) posts for product announcements and artist highlights, and track how they perform. Use when the user wants to draft a LinkedIn or X post, announce a feature or launch, highlight an artist or a result, plan a content calendar, decide on a CTA, or asks "how did my last post do," "what should I post," "write a post about X," or "turn this into a post/thread." This is the post copy + publishing + measurement workflow. Producing a demo video is a separate step — see the demo-video skill. ---- - -# Social Posts - -Write social posts that earn engagement, then prove what worked with real numbers. This skill covers the **copy, the publish, and the measurement** for LinkedIn and X (Twitter). It deliberately does **not** cover producing media (demo videos, screen captures) — keep that distinct so the post workflow applies whether or not there's a video. - -Authenticate to the Recoup API the same way as the `recoup-api` skill: an `x-api-key` (`RECOUP_API_KEY`) or `Authorization: Bearer` (`RECOUP_ACCESS_TOKEN`) header. Never hardcode credentials. - -## The loop - -1. **Learn** — pull how recent posts performed, so copy is grounded in evidence, not vibes. -2. **Draft** — write topic-first copy that fits the platform and the goal. -3. **Decide the CTA** — comment-gate for leads, or direct link for reach. -4. **Publish** — via the connector where supported, or hand off for manual posting. -5. **Log & re-measure** — record what went out; re-pull performance ~48h later. - -Do them in order. Skipping step 1 is how posts drift back to product-led copy that flatlines. - -## Principles that actually move engagement - -These are defaults, not laws — treat each as a hypothesis to keep testing, and let step 1's data override them. - -- **Lead topic-first, not product-first.** Open with the reader's problem or an industry question ("What's your catalog worth?"), not your brand or release notes ("X by Recoup," "We shipped…"). People react to ideas they want to be seen agreeing with. -- **Educational > launch framing.** "Here's how you'd find this out in 10 seconds" beats "we built a tool." Let the product be the proof, lower down. -- **One concrete proof point.** A single real number or example (a named result, a before/after) outperforms a feature list. -- **Broad industry hashtags, 2–3.** Discoverable topical tags reach people who don't follow you yet. Avoid brand-only tags with no topical hook. -- **Short, or substantive — not in between.** Either a punchy 1–2 liner or a post with a real insight. Avoid medium-length product blurbs. -- **One idea per day.** Don't stack two product posts in the same slot; it splits your own audience and reads as broadcasting. -- **Timing:** for B2B, a weekday mid-day window tends to reach the most people. Hold timing constant for a couple of weeks so it stops being a confounding variable when you read results. -- **Platform fit:** LinkedIn rewards comments and dwell; X rewards brevity and punishes link-in-post reach. Tailor per platform — don't cross-post identical copy. - -## Step 1 — Learn from recent posts - -Rank recent posts by engagement before writing. Reactions are the cleanest signal the API exposes for personal posts; **comments are the real signal for lead-gen posts** (see CTA below). - -Pull reactions for a known post via the connector (`POST /api/connectors/actions`): - -```bash -curl -sS -X POST "https://api.recoupable.com/api/connectors/actions" \ - -H "Authorization: Bearer $RECOUP_ACCESS_TOKEN" -H "Content-Type: application/json" \ - -d '{"account_id":"","actionSlug":"LINKEDIN_LIST_REACTIONS", - "parameters":{"entity":"urn:li:ugcPost:"}}' -``` - -Notes that save you time: -- Entity formats: `urn:li:activity:`, `urn:li:ugcPost:`, or `urn:li:share:`. A feed URL's `ugcPost` id usually resolves as `urn:li:ugcPost:`; `urn:li:share:` may 404 for the same post. Try the activity/ugcPost form first. -- A post's UTC time is encoded in its numeric id: `timestamp_ms = id >> 22`. -- LinkedIn's API does **not** expose comments/impressions for **personal** posts, and post bodies often return `403`. Company-page posts unlock `LINKEDIN_GET_SHARE_STATS` / `LINKEDIN_GET_ORG_PAGE_STATS` (impressions, clicks) when reconnected with org-admin scope. -- There's no "list my posts" endpoint for personal accounts — feed post URLs/ids must be supplied. -- X analytics: `TWITTER_GET_POST_ANALYTICS`, plus `TWITTER_LIST_POST_LIKERS`, `TWITTER_GET_POST_RETWEETS`. - -Read the top performer against the flatliners and name the differences (framing, hashtags, CTA, timing). Those differences are your copy brief. - -## Step 2 — Draft - -Write to the brief from step 1 plus the principles above. Structure that travels well: - -``` - - - - - - - - -<2–3 broad industry hashtags> -``` - -House style: avoid em-dashes and obviously-templated phrasing — it reads machine-written. Prefer plain periods and the words a person would actually say. Mirror the voice of the account's past winners. - -## Step 3 — Choose the CTA - -| CTA | Use when | Mechanics | Trade-off | -|-----|----------|-----------|-----------| -| **Comment-gate** ("Comment WORD and I'll send the link") | The goal is leads / a DM list | Drives comments (algorithm boost) + a list of warm leads to DM | Link isn't in the post; slightly higher friction | -| **Direct link** | The goal is reach / immediate trial | Link in post (LinkedIn) or, on X, in a reply not the main post | Fewer comments, less lift, but frictionless | - -If you comment-gate, prepare the reply/DM now so you can respond fast (speed compounds reach): - -> Just sent it 🙌 Here's the link: . Takes ~10 seconds. - -Comment-gate on LinkedIn; use a direct link (often in the first reply) on X. - -## Step 4 — Publish - -Pick the path by platform and media type. **Confirm the exact parameter schema first** with `GET /api/connectors/actions` (it returns each action's JSON Schema) before composing a call. - -**LinkedIn** -- Text or text + link card → `LINKEDIN_CREATE_LINKED_IN_POST` (`author`, `commentary`; add `contentLandingPage` for a link) or `LINKEDIN_CREATE_ARTICLE_OR_URL_SHARE`. -- Text + image → stage the image (`LINKEDIN_INITIALIZE_IMAGE_UPLOAD` / `LINKEDIN_REGISTER_IMAGE_UPLOAD`, or `POST /api/connectors/files` with a public image URL), then pass the image URN in `LINKEDIN_CREATE_LINKED_IN_POST`'s `images` array. -- Comment on a post → `LINKEDIN_CREATE_COMMENT_ON_POST`. -- **Native video: not supported.** The LinkedIn connector exposes image upload only (no video upload/register action). Post videos **manually**. Get the author URN from `LINKEDIN_GET_MY_INFO`. - -**X (Twitter):** post with `TWITTER_CREATION_OF_A_POST` (`text`, optional `media_media_ids`; reply via `reply_in_reply_to_tweet_id`). Images upload via `TWITTER_UPLOAD_MEDIA`. - -Native video works, but the bytes can't go inline: `media` won't fetch a URL, and base64 hits the API's ~4.5MB body limit (`413`). Stage it server-side, then attach: -1. `POST /api/connectors/files` with `{url, toolSlug:"TWITTER_UPLOAD_LARGE_MEDIA"}` — the server fetches the mp4 (host it anywhere public; read once) and returns `{s3key, name, mimetype}`. -2. `TWITTER_UPLOAD_LARGE_MEDIA` with `parameters.media = {s3key, name, mimetype}` (the descriptor object, not a string) and `media_category:"tweet_video"`; wait for `processing_info.state: "succeeded"`. -3. `TWITTER_CREATION_OF_A_POST` with the returned `media_media_ids:[id]`; put the link in a reply so the main post keeps its reach. - -## Step 5 — Log and re-measure - -Record each post immediately: account, platform, the post id/URN, UTC time, the CTA used, and the copy. Then **re-pull performance ~48h later** (reactions + comments for gated posts) and compare week-over-week — report deltas, not all-time totals. Feed the result back into step 1 for the next post. - -## Producing a demo video? - -Keep it separate. This skill assumes media already exists (or that the post is text/image/link). If the post needs a product-demo video — capturing the live tool, assembling it, adding a voiceover — run that as its own asset-production step and attach the result here. Decoupling means this post workflow works the same whether today's post is a video, an image, or plain text. diff --git a/skills/song-writing/SKILL.md b/skills/song-writing/SKILL.md deleted file mode 100644 index 2b107de..0000000 --- a/skills/song-writing/SKILL.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -name: songwriting -description: Guide for writing and evaluating song lyrics and concepts (no audio needed). Use when brainstorming song ideas, writing lyrics, evaluating song drafts, refining a song's written lyrical/melodic hook, or improving an unfinished song. Applies the 7 C's framework to ensure songs are memorable, clear, and authentic. For finding the clip-worthy moment in a finished recording, use recoup-song-hook (that needs an audio file). ---- - -# Songwriting - -Apply the 7 C's framework when writing or evaluating songs. - -## The 7 C's Checklist - -Evaluate every song against these criteria: - -| C | Question | -|---|----------| -| **Concept** | Can you summarize the point in one sentence? | -| **Clever** | Is your concept or twist truly fresh? Does it have an "aha" moment? | -| **Clear** | Is every line easily understandable and does it illustrate the concept? | -| **Concise** | Are lines non-wordy with space for breath and each syllable? | -| **Catchy** | Are the lyrics and melody infectious and memorable? | -| **Consistent** | Do all hook lines relay the same message? | -| **Conversational** | Does it feel personal? If you wouldn't say it, don't sing it. | - -## Workflow - -1. **Draft** — Write freely without self-editing -2. **Evaluate** — Run the draft through all 7 C's -3. **Identify weak points** — Note which C's fail -4. **Revise** — Address each weakness specifically -5. **Re-evaluate** — Confirm all 7 C's pass - -## Quick Tests - -- **Concept test**: State the song's point in one sentence before writing -- **Conversational test**: Read lyrics aloud as speech—if it sounds unnatural, rewrite -- **Hook test**: Can someone sing the chorus back after one listen? -- **Trim test**: Remove a word from each line—if meaning stays, keep it removed diff --git a/skills/streaming-growth/SKILL.md b/skills/streaming-growth/SKILL.md deleted file mode 100644 index d019bc0..0000000 --- a/skills/streaming-growth/SKILL.md +++ /dev/null @@ -1,174 +0,0 @@ ---- -name: streaming-growth -description: Strategy for growing a new or early-stage music artist to key streaming milestones on Spotify and other DSPs. Use when someone asks about getting more streams, unlocking Spotify Showcase or Marquee, increasing Spotify Popularity score, playlist pitching, DSP advertising, or growing an artist from near-zero. Triggers on "how do we get more streams," "unlock Spotify ads," "playlist push," "get to a thousand listeners," "artist growth strategy," or "Spotify Popularity score." ---- - -# Streaming Growth - -A playbook for getting new artists past the streaming milestones that unlock platform advertising tools, algorithmic boosting, and organic discovery. - -## Why Thresholds Matter - -Streaming platforms gate their most powerful tools behind minimum listener counts. Until you hit these numbers, you're locked out of the in-platform advertising and discovery features that make growth compound. - -The strategic goal is not just "get more streams." It's: get to the threshold as efficiently as possible so you can start using the platform's own tools to grow. - -## Key Thresholds - -| Platform | Threshold | What It Unlocks | -|----------|-----------|-----------------| -| Spotify Showcase | 1,000 monthly listeners | In-app ad placement on Spotify homepages. Lower cost, lower friction than external ads — listeners don't leave the app | -| Spotify Marquee | 5,000 monthly listeners (month-over-month) | Full-screen recommendations to targeted listeners when they open Spotify | -| Spotify Popularity 30 | ~30 on 0-100 scale | Baseline algorithmic consideration. Easy to reach with a few hundred consistent listeners | -| Spotify Popularity 50 | ~50 on 0-100 scale | Algorithmic boosting kicks in. Discovery mode benefits. Potential radio pushes. This is where growth starts compounding | -| Apple Music Artist Spotlight | Varies | Featured placement in Browse tab. Requires distributor relationship and editorial pitch | -| Amazon Music Unlimited | Active catalog | Access to Amazon's Alexa-driven discovery and voice-search recommendations | - -**Spotify Popularity** is a 0-100 score that drives the core algorithm. New artists start around 10. It's relatively easy to push to 30 with some audience development. 100 is Drake/Taylor Swift territory. Crossing 50 is where you start getting real algorithmic lift without paying for it. - -## The First Milestone: 1,000 Monthly Listeners - -This is the most important threshold for a brand-new artist. Once you hit 1,000 monthly Spotify listeners, you can run **Showcase campaigns** — in-app ads that put your music front and center on Spotify homepages. - -The key insight: getting people to stream from *within* Spotify is dramatically more effective than driving them from social media to Spotify. When you run ads inside the platform, the listener doesn't have to leave the app, open a link, or switch contexts. That friction reduction matters enormously at small scale. - -### Three Paths to 1,000 - -#### Path 1: Third-Party Playlist Pitching - -**What it is:** Pay a service to pitch your track to independent playlist curators. - -**How it works:** -1. Choose a playlist pitching service (SubmitHub, Playlist Push, Groover, or similar) -2. Write a pitching brief: genre, mood, comparable artists, what makes the track stand out, target playlist types -3. Submit your track with the brief — the service pitches to curators who run playlists in your genre -4. Curators who like it add it to their playlists -5. Playlist listeners discover your track organically - -**What a good pitching brief includes:** -- Genre and subgenre (be specific — "indie bedroom pop" not just "indie") -- 3 comparable artists the target audience already listens to -- The mood or moment the song fits (workout, late night drive, study session) -- One sentence on what makes this track different -- Release date and any timing context - -**Benchmarks (Q1 2026):** -- $500 spend -- Pitched to ~50 playlists -- ~16 playlists picked it up -- Combined follower reach: ~600,000 -- Track reached Spotify Popularity 37 - -**Why this works for new artists:** -- Listeners are already in Spotify, already in browse/discover mode -- Placement on genre-specific playlists tells the algorithm *what kind of artist you are* -- If your track lands on a "sad girl songs" playlist that performs well, you learn that's your audience - -**What to look for in results:** -- Which playlist genres performed best? -- What's the audience makeup of playlists that picked you up? -- Did the Spotify Popularity score move? - -#### Path 2: Social-to-DSP Advertising - -**What it is:** Run paid ads on social platforms (Meta, TikTok) that link directly to DSPs. - -**How it works:** -1. Create ad creative (short video clip + call to action) -2. Target audience segments likely to enjoy the music -3. Ad links to Spotify/Apple Music -4. Track click-through and listen rates - -**Cost benchmarks (Q1 2026):** -- CPC (cost per click): ~$0.34 -- Click-to-listen conversion: ~30% of clicks result in an actual listen -- Effective cost per listen: ~$1.13 - -**When to use this path:** -- When you want granular control over audience targeting -- When you want to learn which demographics respond to the music -- When you have strong social content that works as ad creative - -**Limitations:** -- Higher friction than in-platform ads — listener has to leave the social app -- More expensive per listen than playlist pitching -- Click does not equal listen — many people click but don't stay - -#### Path 3: Organic Content + Algorithm - -**What it is:** Post consistent social content using the music, grow the audience naturally, hope for algorithmic lift. - -**When to use this path:** -- As a baseline alongside paid strategies -- When budget is extremely limited -- For long-term brand building - -**Limitations:** -- Slowest path to 1,000 -- Social engagement doesn't directly translate to streams without a catalyst -- YouTube subscribers and TikTok views don't automatically become Spotify listeners - -Focus content on tactics that convert social views into streams: a consistent posting cadence, release-day and pre-save pushes, and short-form clips that link directly to the track. - -### Recommended Approach - -For a brand-new artist, combine paths: - -1. **Baseline:** Organic content posting daily (Path 3) — this builds the brand regardless -2. **Primary push:** Playlist pitching (Path 1) — best bang for buck at this stage, and the audience data is more valuable than click-through data -3. **Supplementary:** Small social ad budget (Path 2) — use this to learn which audiences respond, feed those learnings into future targeting - -The goal is not just to hit 1,000 streams. It's to hit 1,000 streams *with an audience you understand* so that when you unlock Showcase, you know exactly who to target. - -## After 1,000: Running Showcase Campaigns - -Once you've unlocked Showcase: - -**Default audience:** People who already listen to you or similar artists -**Expanded audience:** Include people who *may* like your release (broader targeting) - -Showcase puts your track on Spotify homepages. The listener is already in the app, already in a listening mindset. No app-switching, no link-clicking — just tap and listen. - -**Strategy:** -- Start with default audience to reinforce your existing listener base -- Expand gradually to test broader appeal -- Monitor which audience segments have the highest listen-through rate - -## Spotify Popularity Score - -The Popularity score (0-100) is the single most important algorithmic signal on Spotify. - -| Score Range | What It Means | -|-------------|---------------| -| 0-10 | Brand new, no traction | -| 10-30 | Some listeners, easy to reach with basic promotion | -| 30-50 | Growing. Algorithm is starting to notice. Playlist editorial consideration begins | -| 50-70 | Algorithmic boosting active. Discovery mode benefits without paying for discovery mode. Radio push potential | -| 70-100 | Major artist territory. Full algorithmic support | - -**How to move the score:** -- Consistent daily listeners (not spikes — the algorithm rewards sustained listening) -- Playlist placements with real engagement (saves, adds to library) -- Listener retention (people finishing your songs, not skipping) -- Growing month-over-month, not flat - -**The compounding effect at 50+:** -Once you cross ~50 Popularity, you benefit from algorithmic recommendations without paying for them. This is where you can start pulling back on paid promotion because the platform is doing organic discovery for you. Getting a track to Popularity 50 is the strategic goal that lets you ease off paid ads. - -## Decision Framework - -When you're trying to hit a threshold, ask: - -1. **What's the fastest path?** Time matters — every day below threshold is a day you can't use in-platform tools -2. **What teaches us the most?** Getting 1,000 streams from a sad girl playlist tells you more about your audience than 1,000 streams from untargeted ads -3. **What's the cost per learn?** Sometimes the slightly more expensive path gives you data that makes everything after it cheaper - -## Tracking Progress - -Monitor these weekly: - -- Monthly listener count (target: 1,000, then 5,000) -- Spotify Popularity score (target: 30, then 50) -- Playlist placements (count + combined follower reach) -- Save rate (saves / streams — high save rate = strong signal) -- Listener retention (average listen duration / song duration) diff --git a/skills/trend-to-song/SKILL.md b/skills/trend-to-song/SKILL.md deleted file mode 100644 index 9042ea4..0000000 --- a/skills/trend-to-song/SKILL.md +++ /dev/null @@ -1,172 +0,0 @@ ---- -name: trend-to-song -description: Create songs and content campaigns from trending cultural moments. Use when someone identifies a trending topic, viral moment, reality TV drama, meme, or cultural event and wants to turn it into a song and distribution strategy. Also triggers on "make a song about this trend," "capitalize on this moment," "reverse engineer from the market," "cultural moment to song," "trend-jack," or "burner page strategy." This flips the traditional music workflow — instead of song-first then marketing, you go market-first then song. ---- - -# Trend-to-Song Pipeline - -Turn trending cultural moments into songs and test campaigns in 72 hours or less. - -## Why This Exists - -Traditional music: write a song, then figure out how to market it. -This workflow: spot a cultural moment, build a song around it, test market fit fast. - -AI song generation makes this possible. You can produce a finished track in hours, not weeks. The bottleneck is no longer production — it's speed of trend identification and execution. - -## The Core Insight - -Cultural moments create temporary demand for content. Someone will monetize that demand with music. The question is whether it's you or someone else. Speed wins. - -**Real example:** When a major reality TV betrayal storyline explodes, fans flood TikTok with edits. Whoever's song soundtracks those edits captures the wave. This happened with Alex Warren and The Ordinary — a song became synonymous with a cultural moment because someone moved fast. - -## Pipeline Overview - -``` -Day 0: Spot the trend -Day 0: Extract emotional register + key phrases -Day 0: Generate the song -Day 1: Create burner/fan page -Day 1: Post first edits using the song -Day 2-3: Monitor response, iterate -``` - -## Step 1: Spot the Trend - -Identify cultural moments with these qualities: - -- **High emotional charge** — drama, betrayal, heartbreak, triumph -- **Active fan edit community** — people are already making content about it -- **No dominant soundtrack yet** — the moment hasn't been "claimed" by a song -- **Relatability** — the situation maps to universal emotions (unrequited love, friendship betrayal, coming of age) - -**Sources to monitor:** -- Reality TV discourse (Bravo, Netflix, dating shows) -- Celebrity drama and breakups -- Viral TikTok storylines -- Meme formats with emotional undertones -- Seasonal/cultural events (prom season, breakup season, graduation) - -## Step 2: Extract the Emotional DNA - -Before generating lyrics, identify: - -1. **Emotional register** — What does the audience *feel* about this moment? (heartbreak, rage, vindication, bittersweet nostalgia) -2. **Key phrases and slang** — What language is the internet using? Specific phrases from the source material, trending slang, inside jokes -3. **The audience** — Who is making edits and engaging? (teenage girls, reality TV fans, a specific fandom) -4. **Comparable songs** — What existing songs are being used in edits right now? Match that vibe - -**Deliverable:** Send the song creator a brief with: -- 3-5 key phrases or words to weave in -- The target emotional feel (e.g., "sad girl anthem," "vindication banger," "bittersweet nostalgia") -- Reference tracks for vibe -- Any specific lyrics or hooks you want included - -## Step 3: Generate the Song - -Use AI music generation to produce the track. - -**Guidelines:** -- Match the emotional register from Step 2 -- Weave in the identified phrases naturally — they should feel like lyrics, not forced references -- Keep it short — 2:00 to 2:30 is ideal for social content -- The hook should be immediately usable as a 15-second clip -- Produce in a genre that fits the audience (sad girl pop, lo-fi bedroom, etc.) - -**Output:** MP3 file ready for content creation - -## Step 4: Set Up Distribution - -**Create a burner/fan page** — not the main artist profile. - -Why a burner page: -- Tests market fit without risking the artist's brand -- Can be positioned as a "fan account" or "edits account" -- If it flops, no harm done -- If it pops, you can connect it back to the artist - -**Page setup:** -- Name it something relevant to the trend or fandom -- Bio should reference the cultural moment -- Post 2-3 pieces of content before the song content to establish the page - -## Step 5: Create and Post Content - -**Types of content to create:** -- Fan edits using clips from the trending moment + the new song -- Text-on-screen videos with relatable captions tied to the moment -- "POV" style content that puts the viewer in the emotional situation -- Duets/stitches with viral posts about the moment - -**Posting strategy:** -- Post 3-5 pieces on Day 1 -- Use the song as the audio on every post -- Tag relevant hashtags for the trend -- Post at peak engagement times for the target audience - -## Step 6: Monitor and Learn - -**Track within 48-72 hours:** -- Which edits get the most views? -- Is anyone else using the song audio? -- What's the save-to-view ratio? (saves = strong signal) -- Are people commenting about the song specifically? -- Does the audio page show organic usage? - -**Decision framework:** -- **If it catches:** Double down. More content. Consider connecting to artist profile. Create more songs for adjacent moments -- **If it's flat:** Analyze why. Wrong emotional register? Bad timing? Oversaturated moment? Learn and move faster next time -- **Either way:** You now have data on what this audience responds to - -## Speed Benchmarks - -The value of this workflow is speed. Track your execution time: - -| Phase | Target | Max | -|-------|--------|-----| -| Trend spotted to brief sent | 2 hours | 4 hours | -| Brief to finished song | 4 hours | 12 hours | -| Song to first content posted | 6 hours | 24 hours | -| Total pipeline | 12 hours | 48 hours | - -If you're consistently taking longer than 48 hours end-to-end, the trend may have passed by the time you post. Speed is the entire point. - -## What Makes a Good Brief - -**Good brief:** -> Bravo show drama: best friend secretly dating her friend's ex post-divorce. Internet is furious on behalf of the betrayed friend. Audience is women 18-35. Emotional register: vindication + heartbreak. Key phrases: "she knew the whole time," "bestie betrayal." Vibe: Olivia Rodrigo meets Gracie Abrams. Make it a sad girl anthem that sounds like it could be the betrayed friend's internal monologue. - -**Bad brief:** -> Make a song about reality TV drama. - -The more specific the brief, the better the song fits the moment. - -## Case Study Template - -After each pipeline run, document what happened: - -``` -## Trend: [What happened] -**Spotted:** [Date/time] -**Song delivered:** [Date/time] -**First post:** [Date/time] -**Total pipeline time:** [X hours] - -**Results (72h):** -- Total views: -- Best performing edit: [views, type] -- Audio reuses by others: -- Save rate: - -**Learnings:** -- What worked: -- What didn't: -- Next time: -``` - -## Integration with Recoupable - -- Use the content agent to generate video edits once the song exists -- The song can be added to an artist's catalog via the standard song pipeline -- Burner page content can be cross-posted to the artist's channels if it performs -- Track performance data through the standard analytics pipeline diff --git a/skills/valuation-sales-pipeline/SKILL.md b/skills/valuation-sales-pipeline/SKILL.md deleted file mode 100644 index 0ef73db..0000000 --- a/skills/valuation-sales-pipeline/SKILL.md +++ /dev/null @@ -1,188 +0,0 @@ ---- -name: valuation-sales-pipeline -description: >- - Work the Recoup "Valuation Leads" sales funnel in Attio — take an inbound - catalog-valuation lead and progress it from "valuation ran" to "signed up for - Pro." Researches who the lead is and their relationship to the artist they - valued, qualifies the catalog against revenue goals, enriches and advances the - Attio CRM record, and drafts the first outreach email plus a clean one-page - valuation PDF. Use when asked to "work the valuation funnel", "qualify this - lead", "who ran this valuation", "is this catalog worth our time", "update the - Attio pipeline", "advance this lead", "draft outreach for this valuation", or - "turn valuation leads into Pro signups". Requires an Attio API key and a Recoup - API key. ---- - -# Valuation Sales Pipeline - -Turn inbound **catalog-valuation leads** into **Recoup Pro signups**. When someone -runs Recoup's free "what is my catalog worth?" valuation, a lead lands in the Attio -**Valuation Leads** funnel at stage **New**. This skill is the repeatable motion for -working that lead to a paying customer. - -The valuation is the wedge; the product is **growing the catalog's value** (recover -uncollected royalties + grow streams + document it for a higher multiple). The job is -to prove that quickly enough that the lead converts. - -## When to use - -- "Work / triage the valuation funnel." "Who's in the pipeline?" -- "Who ran this valuation and what's their relationship to the artist?" -- "Is this catalog worth our time?" (qualify against goals) -- "Fill in the Attio profile and advance this lead." -- "Draft the welcome email + a clean valuation PDF for this lead." - -## Prerequisites - -- `ATTIO_API_KEY` — an Attio access token (read-write on records + list entries). -- `RECOUP_API_KEY` (`recoup_sk_…`) — for catalog sizing via the Research API; mint one - with `POST https://api.recoupable.com/api/agents/signup`. Load the - `music-industry-research` and `catalog-value-estimator` skills for endpoint detail. -- `reportlab` for the PDF: `pip install reportlab --break-system-packages`. -- Scripts ship in this skill's `scripts/`; run them from the skill directory as - `python3 scripts/.py`. - -The funnel's stages, field slugs, and the exact Attio API calls are documented in -`references/attio-funnel.md`. Pulling a lead's catalog — album art + live streams — -straight from the public Recoup APIs (the same ones the marketing valuation tool uses, -no scraping) is documented in `references/recoup-valuation-api.md` and automated by -`scripts/fetch_catalog.py`. The qualification rubric is in -`references/qualification-rubric.md`. The email template is in -`templates/outreach-email.md`. A spoofed end-to-end example is in `fixtures/`. - -## The pipeline (stages) - -``` -New → Report Delivered → Qualified → Pro Offer Sent → Pro Active (Won) → Lost -``` - -`New` = valuation ran (auto). `Pro Active (Won)` = a Stripe Pro subscription is live — -the one true win. `Lost` requires a Lost Reason. See `references/attio-funnel.md`. - -## The report PDF (what you send) - -`scripts/render_valuation_pdf.py` produces a 3-part PDF whose order is deliberate: - -1. **Page 1 — executive summary.** The headline value + range and the top releases by streams - (with album art) — the *same figures the contact saw in the tool*. Its one job is to build - trust on first contact by proving the report is accurate to what they already saw. Lead with - accuracy, not a pitch. -2. **Page 2 — an honest reading.** Grounded, specific observations from the page-1 data - (concentration, range caveats, master-side scope). **No unverified claims** about what Recoup - "will" recover or grow — only what the data shows. -3. **Appendix — full source data.** Every measured release (album art + year + tracks + streams), - matching the tool's complete list, so nothing is hidden behind the summary. - -Build the data with `scripts/fetch_catalog.py` (below), add the dollar band, then render. - -## Workflow - -### 1. Research the lead - -Pull the lead's Attio record + funnel entry (`references/attio-funnel.md` has the calls). -Capture: `looked_up_artist`, `est_catalog_value`, `lifetime_streams`, `follower_count`, -`spotify_artist_url`, `email_addresses`, `name`, `primary_location`, `valued_at`. - -Then establish **who they are and how official they are to the catalog** — this decides -the outreach tone and whether they can actually authorize work: - -- Web-search the artist + the person/brand behind the email. Compare the email handle to - the looked-up artist (self-search of own brand vs. a third party). -- **Verify the person's *current* role, not a stale one.** People change companies and keep - legacy profile handles — pick the LinkedIn whose handle/email domain matches the lead's email, - and confirm their *present* employer (a title pulled from an old campaign can be years out of - date and will mis-set the Relationship). LinkedIn and Instagram block `WebFetch` (HTTP 999) — - read them via the **authenticated browser (chrome-devtools MCP)**. -- Classify the **Relationship**: `Owner/Operator` (the artist or label/brand itself) · - `Label` · `Manager` · `Collaborator` (released work with the artist) · `Fan/Other` · - `Unknown`. Owners/labels/managers can authorize an engagement; collaborators are warm - insiders; fans rarely convert. Keep the lead JSON `relationship` and the Attio field in sync — - if you re-classify after deeper research, update **both** (the PDF reads the JSON). -- Cross-check recent product activity (signup date, credits used) if you have account - access — a lead who burned most of their credits is highly engaged. - -### 2. Qualify against goals - -Score the lead with `references/qualification-rubric.md`. In short: a real, mid-size -catalog (meaningful lifetime streams and estimated value) held by someone official enough -to say yes, active recently. Decide: - -- **Pursue** → continue to step 3. -- **Backlog** → real but low priority (small catalog or unclear relationship). Leave at - `New`; note why. -- **Disqualify** → move to `Lost` with a Lost Reason (e.g. junk/zero-stream lookup, a fan, - a test row). Don't spend outreach on these. - -### 3. Enrich the Attio profile - -For a pursued lead, write back to the Attio record/entry (`references/attio-funnel.md`): - -- Person record: set `name` (real person or brand), and socials if known. -- Funnel entry: set `Catalog Value`, `Relationship`, and `Owner` (the Recoup rep working - it). These make the board sortable by $ and accountable. -- Advance the stage to **Report Delivered** only once the email + PDF actually go out - (step 4). Until then it stays `New`. - -### 4. Draft outreach + valuation PDF - -- Pull the catalog + album art straight from the public APIs (no scraping): - `python3 scripts/fetch_catalog.py --artist-id --out lead.json` — fills streams, - album art, and release counts. Add the dollar band (from the tool, or computed with the - `catalog-value-estimator` skill) to `lead.json`. Don't infer "dormant" from a `$0` row in the - live UI — confirm against the measurements endpoint (see `references/recoup-valuation-api.md`). -- **Onboard the valued artist under the lead's own Recoup account and pull exact socials over the - API** (part of working the lead, not overkill). The self-contained recipe — resolve `account_id` - via `POST /api/accounts {email}` (the email→`account_id` lookup; no Supabase), create the artist, - `PATCH` profile URLs, scrape, poll — is in `references/recoup-valuation-api.md`. Put the `account_id` - in lead JSON `account.owner_account_id` so the PDF admin line carries it. -- Fill the lead JSON `socials` block so the report renders the "Artist channels" line. Use the - **exact** `follower_count` + **real** `bio` from the scrape (blank, never filler, if unverified), - and cross-check the **same Spotify artist id** so you don't attach a same-name impostor. -- Render the valuation PDF (headline + a top-releases breakdown with album art, an honest - "reading your result" page, and a full-catalog appendix): - `python3 scripts/render_valuation_pdf.py --lead lead.json --out ./out` - (see `fixtures/example-lead.json` for the shape). -- Draft the first email from `templates/outreach-email.md`. First touch must be **scannable in - 3-5 seconds**: **2 sentences + 3 bullets + 1 CTA**, from the rep (not a generic address). The PDF - carries the depth - keep caveats and detail out of the email. Don't pitch the engagement here; - that's the call. -- After it's sent, move the entry to **Report Delivered** and log a note of what was sent. - -## Notes & caveats - -- **PII / privacy.** Leads are real people. Keep names, emails, and account IDs inside the - CRM; the bundled `fixtures/` use spoofed data only. Never paste a lead's PII into shared - docs or external services. -- **Relationship drives capacity to pay** - a single six-figure catalog rarely justifies a large - retainer; convert to Pro and/or a managed engagement, or run the strongest as a pilot. Sizing and - routing detail in `references/qualification-rubric.md`. - -## Lessons from live runs (ICEBOX · Chilled Cat · Eurotripp) - -The API/measurement gotchas live in `references/recoup-valuation-api.md`; the report-judgment ones -are spelled out here because they shape what you write. - -- **Before you trust a run, watch for these (all detailed in `references/recoup-valuation-api.md`):** - the auth route-split (hex `x-api-key` works on `/spotify/*` only; `/research/*` needs a Bearer - token that expires ~1h) · 0-streams-across-everything means auth/expiry, not a dormant catalog · a - credit-exhausted free run undercounts, so re-measure the full catalog and reframe as "we finished - your interrupted run" · the dollar model is trailing-12-month-driven, so a single-snapshot lead - can't be priced same-day. -- **Gross streams ≠ owned value - read it honestly:** - - *Collaborations / compilations* (Chilled Cat's "Vibes" releases are each Chilled Cat × a - different producer): the owner holds only a **split** of each track, so realizable value is below - the gross. Say so, and point to a statement to pin the splits. - - *Cover songs* (Eurotripp's "Where's Your Head At" = ~95% of streams, a Basement Jaxx cover): the - artist owns the **master, not the publishing**. Lead with master royalties + neighboring rights - (SoundExchange) + Content ID, not catalog publishing. - - Curate the displayed release list so a single that also sits on a compilation isn't shown twice - (lifetime totals are already deduped by track id in `fetch_catalog.py`). -- **Pre-cache album art before rendering.** Downloading 100+ covers inline gets `i.scdn.co` throttled - and `render_valuation_pdf.py` silently drops the failed images. Pre-fetch covers to local files - (retries + a small delay) and point the lead JSON `image` fields at `file://` paths. -- **Keep page-2 reading concise, and use plain hyphens (no em/en dashes) in all outbound copy** - - long reading notes push the footer onto a near-empty extra page, and em dashes read as AI-generated - to recipients. The render script auto-generates the concentration paragraph from the data, so add - only **2-3 short `reading_notes`** of your own and **don't repeat the concentration point** - that - duplicate is what tips page 2 over. Expect a **4-page** render (cover · reading+channels · - Appendix A x2); a near-empty page means you overflowed - trim a `reading_note`. diff --git a/skills/valuation-sales-pipeline/fixtures/Neon-Tide-valuation.pdf b/skills/valuation-sales-pipeline/fixtures/Neon-Tide-valuation.pdf deleted file mode 100644 index 326bd18c2c704f0e0e4305b0678961f7136a0f52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7155 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 IN$<=50vTKL$p8QV diff --git a/skills/valuation-sales-pipeline/fixtures/example-lead.json b/skills/valuation-sales-pipeline/fixtures/example-lead.json deleted file mode 100644 index 331ed78..0000000 --- a/skills/valuation-sales-pipeline/fixtures/example-lead.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "artist": "Neon Tide", - "email": "neontidemusic@example.com", - "name": "Alex Rivera", - "relationship": "Owner/Operator", - "primary_location": "Austin, Texas, US", - "spotify_artist_url": "https://open.spotify.com/artist/0000000000EXAMPLE0000", - "est_catalog_value": 312500, - "value_low": 213000, - "value_high": 421000, - "lifetime_streams": 41200000, - "follower_count": 47800, - "valued_at": "2026-06-20", - "lead_source": "Catalog Valuation", - "account": { - "owner_email": "alex@example.com", - "owner_account_id": "00000000-0000-4000-8000-000000000000", - "artist_account_id": "11111111-1111-4111-8111-111111111111" - }, - "socials": [ - { - "platform": "Spotify", - "handle": "Neon Tide", - "url": "https://open.spotify.com/artist/0000000000EXAMPLE0000", - "followers": 47800, - "bio": "Artist profile" - }, - { - "platform": "Instagram", - "handle": "@neontidemusic", - "url": "https://www.instagram.com/neontidemusic/", - "followers": 12500, - "bio": "indie / electronic · Austin TX" - }, - { - "platform": "TikTok", - "handle": "@neontidemusic", - "url": "https://www.tiktok.com/@neontidemusic", - "followers": 8300, - "bio": "new music every friday" - } - ], - "reading_notes": [ - "The dominant release is a collaboration. Most of the value sits in a co-credited track, so the owner's realizable share depends on master/publishing splits a statement would confirm.", - "Big streams, smaller owned audience. The catalog leans on a breakout moment more than a dedicated fanbase - the clearest place to build durable value is converting listeners into followers." - ], - "roster": { - "label": "Example Label", - "note": "Neon Tide is one of several artists on the roster. The same baseline runs on any of them - a sample by Spotify following:", - "artists": [ - { - "name": "Artist Two", - "followers": 14200 - }, - { - "name": "Artist Three", - "followers": 6100 - }, - { - "name": "Artist Four", - "followers": 2300 - } - ], - "more_count": 5, - "cta": "Want the same valuation + socials baseline for the rest of the roster? Reply and we'll run it.", - "source": "examplelabel.com/artists", - "full": [ - { - "name": "Neon Tide", - "followers": 47800 - }, - { - "name": "Artist Two", - "followers": 14200 - }, - { - "name": "Artist Three", - "followers": 6100 - }, - { - "name": "Artist Four", - "followers": 2300 - }, - { - "name": "Artist Five", - "followers": 980 - }, - { - "name": "Artist Six", - "followers": null - } - ] - }, - "_note": "Spoofed sample lead - all PII (name, email, Spotify ID) is fictional." -} \ No newline at end of file diff --git a/skills/valuation-sales-pipeline/fixtures/example-pipeline-brief.md b/skills/valuation-sales-pipeline/fixtures/example-pipeline-brief.md deleted file mode 100644 index 7106e84..0000000 --- a/skills/valuation-sales-pipeline/fixtures/example-pipeline-brief.md +++ /dev/null @@ -1,45 +0,0 @@ -# Example pipeline brief (spoofed) - -> A sanitized example of the qualification output this skill produces from a real funnel -> run. **All names, emails, artists, and IDs below are fictional** - illustration only. - -## Top leads, by catalog value (valued in the last ~24h) - -| Email (spoofed) | Holder | Artist valued | Followers | Lifetime streams | Est. value | Relationship | Route | -|---|---|---|---|---|---|---|---| -| neontidemusic@example.com | Alex Rivera | Neon Tide | 47.8k | 41.2M | $312,500 | Owner/Operator | **Pursue** | -| lunalabel@example.com | Luna Collective (label) | Sora Vale | 51.0k | 33.9M | $268,400 | Label | **Pursue** | -| dj-paperclip@example.com | (collaborator) | Paperclip | 38.1k | 49.5M | $284,900 | Collaborator | **Pursue** | -| midtempo@example.com | - | Mild Current | 7.1k | 4.4M | $16,200 | Unknown | Backlog (small) | -| fan-typed-this@example.com | - | The Bedroom Demos | 0.2k | 5.5k | $220 | Fan/Other | Lost (junk) | -| pr-test@example.com | - | (seed row) | - | - | - | Unknown | Lost (test row) | - -## Why the top three qualify - -- **Real, mid-size catalogs** - tens of millions of lifetime streams, six-figure estimated - value: actual recurring earnings to grow and recover against. -- **Official enough to say yes** - owner, label, and an insider collaborator. -- **Recent + intent** - all valued within a day; high engagement. - -## Next actions (per pursued lead) - -1. Enrich the Attio entry: set `catalog_value`, `relationship`, `owner`; keep stage at `New`. -2. Render the valuation PDF (`scripts/render_valuation_pdf.py`). -3. Draft the welcome email (`templates/outreach-email.md`) with one free insight. -4. Send, then advance to **Report Delivered** and log a note. - -## Example filled outreach (spoofed) - -> **Subject:** Neon Tide - your catalog valuation (+ one thing I'd fix first) -> -> Hi Alex, I'm Sam from Recoup - I saw you just ran a valuation on **Neon Tide**. Here's the -> full breakdown: **$312,500**, off 41.2M lifetime streams. [attach: Neon-Tide-valuation.pdf] -> -> One thing jumped out: several high-traffic playlists your closest peers sit on don't yet -> include Neon Tide, and 41M streams almost always have uncollected neighboring-rights and -> Content-ID income. I can quantify both for free. -> -> I help catalogs like Neon Tide's actually *grow* that number - recover what you're owed and -> lift monthly streams, tracked against this baseline. 15 minutes this week? -> -> - Sam diff --git a/skills/valuation-sales-pipeline/references/attio-funnel.md b/skills/valuation-sales-pipeline/references/attio-funnel.md deleted file mode 100644 index 3927350..0000000 --- a/skills/valuation-sales-pipeline/references/attio-funnel.md +++ /dev/null @@ -1,112 +0,0 @@ -# Attio "Valuation Leads" funnel — schema & API recipes - -The funnel is an Attio **List** named **Valuation Leads** (`api_slug: valuation_leads`) -on the **people** object. Each inbound catalog valuation creates a person record (with the -lead data) and a list entry (the funnel position). - -Auth: `Authorization: Bearer $ATTIO_API_KEY`. Base: `https://api.attio.com/v2`. -List/attribute/option IDs are **workspace-specific** — discover them at runtime, don't -hardcode. - -## Stages (the `stage` status attribute) - -``` -New → Report Delivered → Qualified → Pro Offer Sent → Pro Active (Won) → Lost -``` - -- **New** — valuation ran (auto-entry). Exit within ~24h. -- **Report Delivered** — welcome email + valuation PDF sent. -- **Qualified** — replied / booked / confirmed they own-or-represent the catalog AND value ≥ threshold. -- **Pro Offer Sent** — Pro trial link or pilot terms sent. -- **Pro Active (Won)** — **Stripe Pro subscription active** (the only definition of Won). -- **Lost** — no-go; set a **Lost Reason**. - -## Custom funnel fields (list-entry attributes) - -| Slug | Type | Use | -| --- | --- | --- | -| `catalog_value` | currency (USD) | Point-in-time estimated value; lets the board sort by $. | -| `relationship` | select | `Owner/Operator` · `Label` · `Manager` · `Collaborator` · `Fan/Other` · `Unknown`. | -| `lost_reason` | select | `Price too high` · `Not the owner/rep` · `Didn't trust the number` · `No upside / already managed` · `No response` · `Other`. | -| `owner` | actor-reference | Recoup member accountable for the lead. | - -Lead data (artist, value, streams, followers, Spotify URL) lives on the **person record** -under attributes like `looked_up_artist`, `est_catalog_value`, `lifetime_streams`, -`follower_count`, `spotify_artist_url`, `email_addresses`, `lead_source` (= "Catalog Valuation"). - -## Recipes - -### Discover the list ID - -```bash -curl -s -H "Authorization: Bearer $ATTIO_API_KEY" https://api.attio.com/v2/lists \ - | jq -r '.data[] | select(.api_slug=="valuation_leads") | .id.list_id' -``` - -### List all leads in the funnel (with stage) - -```bash -curl -s -X POST -H "Authorization: Bearer $ATTIO_API_KEY" -H "Content-Type: application/json" \ - -d '{"limit":100}' "https://api.attio.com/v2/lists/$LIST/entries/query" -``` - -Each entry has `parent_record_id` (the person) and `entry_values.stage`. - -### Read the lead's person record - -```bash -curl -s -H "Authorization: Bearer $ATTIO_API_KEY" \ - "https://api.attio.com/v2/objects/people/records/$RECORD_ID" -``` - -### Discover select-option IDs (e.g. relationship) - -```bash -curl -s -H "Authorization: Bearer $ATTIO_API_KEY" \ - "https://api.attio.com/v2/lists/$LIST/attributes/relationship/options" \ - | jq -r '.data[] | "\(.title)=\(.id.option_id)"' -``` - -### Enrich a funnel entry (owner + value + relationship) - -```bash -curl -s -X PATCH -H "Authorization: Bearer $ATTIO_API_KEY" -H "Content-Type: application/json" \ - -d '{"data":{"entry_values":{ - "owner":[{"referenced_actor_type":"workspace-member","referenced_actor_id":"'$ME'"}], - "catalog_value":[{"currency_value":312500}], - "relationship":[{"option":"'$REL_OPTION_ID'"}] - }}}' \ - "https://api.attio.com/v2/lists/$LIST/entries/$ENTRY_ID" -``` - -### Set the person's name / socials - -```bash -curl -s -X PATCH -H "Authorization: Bearer $ATTIO_API_KEY" -H "Content-Type: application/json" \ - -d '{"data":{"values":{"name":[{"first_name":"Alex","last_name":"Rivera","full_name":"Alex Rivera"}], - "instagram":"https://www.instagram.com/example"}}}' \ - "https://api.attio.com/v2/objects/people/records/$RECORD_ID" -``` - -> `avatar_url` is enrichment-only (`is_writable:false`) — it cannot be set via the API. -> Attio populates it from a resolvable identity (email/social). To capture an artist image, -> store the Spotify image URL on a writable custom field instead. - -### Advance the stage (after the email is sent) - -```bash -# resolve the status_id once -curl -s -H "Authorization: Bearer $ATTIO_API_KEY" \ - "https://api.attio.com/v2/lists/$LIST/attributes/stage/statuses" \ - | jq -r '.data[] | "\(.title)=\(.id.status_id)"' - -curl -s -X PATCH -H "Authorization: Bearer $ATTIO_API_KEY" -H "Content-Type: application/json" \ - -d '{"data":{"entry_values":{"stage":[{"status":"'$REPORT_DELIVERED_STATUS_ID'"}]}}}' \ - "https://api.attio.com/v2/lists/$LIST/entries/$ENTRY_ID" -``` - -### Get the artist image (no Recoup quota, no auth) - -```bash -curl -s "https://open.spotify.com/oembed?url=$SPOTIFY_ARTIST_URL" | jq -r .thumbnail_url -``` diff --git a/skills/valuation-sales-pipeline/references/qualification-rubric.md b/skills/valuation-sales-pipeline/references/qualification-rubric.md deleted file mode 100644 index 5526324..0000000 --- a/skills/valuation-sales-pipeline/references/qualification-rubric.md +++ /dev/null @@ -1,56 +0,0 @@ -# Qualification rubric — is this valuation lead worth our time? - -Goal context: every pursued lead should plausibly contribute to **recurring revenue** -(Recoup Pro and/or a managed growth+recovery engagement). Score each lead on four axes, -then route it. - -## The four axes - -1. **Catalog size / value.** Does the catalog have real, sustainable earnings to grow and - recover against? - - Strong: established catalog — tens of millions of lifetime streams and a six-figure+ - estimated value. - - Weak: tiny or zero-stream lookups, near-zero estimated value (often a fan typing in - their own bedroom project, or a test/junk row). - - **Don't trust the tool's number blindly on a large catalog** — a free run can exhaust the - lead's credits partway and report a value built on only part of the catalog (Chilled Cat: - ~27% measured, ~$290K shown vs ~$1.08M on the full catalog). If the lead has a deep catalog, - check remaining credits and re-measure the full catalog before qualifying on value (see - `references/recoup-valuation-api.md` → "Spotting a truncated free run"). - -2. **Relationship / authority.** Can this person actually say yes to an engagement? - - Strong: `Owner/Operator`, `Label`, `Manager`. - - Warm: `Collaborator` (released work with the artist; an insider). - - Weak: `Fan/Other`, `Unknown`. - -3. **Recency / intent.** Did they value recently, and how engaged are they? - - Strong: valued in the last few days; burned a lot of credits / ran multiple lookups. - - Weak: stale, single shallow lookup. - -4. **Capacity to pay.** Is there a business behind it (label, management co., active - independent artist) rather than a hobbyist? - -## Routing - -- **Pursue** — strong on size + authority, recent. Enrich the Attio profile, draft outreach - + PDF, advance toward Pro / a pilot. -- **Backlog** — real catalog but weak relationship or smaller value. Leave at `New`; revisit - if they re-engage. Note why. -- **Disqualify → Lost** — zero/near-zero catalog, a fan, or an internal/test row. Set the - matching `lost_reason` so the funnel teaches you why leads don't convert. - -## Revenue framing (set expectations honestly) - -A six-figure catalog implies only roughly $24k–$36k/yr of net label share, so one catalog -rarely justifies a large standalone retainer. Paths to recurring revenue: - -- **Pro subscription** — fastest, lowest-friction; pair with standing scheduled tasks - (weekly trends, playlist alerts, monthly value-delta) for retention. -- **Managed growth + recovery engagement** — for owners/labels with multiple acts. -- **Risk-reversal pilot** — for the single strongest catalog: recover income, grow streams, - deliver a buyer-ready data room, priced against a documented baseline. - -## Internal / test rows to always disqualify - -Leads on `@example.com` domains, or internal team emails, are seed/test data — never work -them as prospects. diff --git a/skills/valuation-sales-pipeline/references/recoup-valuation-api.md b/skills/valuation-sales-pipeline/references/recoup-valuation-api.md deleted file mode 100644 index 56bd881..0000000 --- a/skills/valuation-sales-pipeline/references/recoup-valuation-api.md +++ /dev/null @@ -1,118 +0,0 @@ -# Recoup valuation API — pull a lead's catalog directly (no scraping) - -The marketing valuation tool (`recoupable.com/valuation`) is just a thin client over **public -Recoup APIs**. Call them directly — `scripts/fetch_catalog.py` does exactly this — instead of -scraping the UI. Base: `https://recoup-api.vercel.app` (or `https://api.recoupable.com`). -Auth: `x-api-key: $RECOUP_API_KEY` (or `Authorization: Bearer $RECOUP_ACCESS_TOKEN`). - -> **Auth gotcha — the key and the token cover different routes.** A hex `x-api-key` authorizes -> `/api/spotify/*` (search, albums, art) but is **rejected by `/api/research/*`** (the per-album -> measurements and the estimator). Those need a **Bearer access token** (`RECOUP_ACCESS_TOKEN`, e.g. -> a Privy session JWT), which **expires in ~1 hour** ("Authentication token expired"). If a catalog -> reads back as **0 streams across every album**, it's almost always this — a hex key or an expired -> token on `/research/*`, not a dormant catalog. Use the Bearer token for measurement work and grab a -> fresh one when it 401s. - -## The flow the UI runs - -1. **Resolve the artist** — `GET /api/spotify/search?q=&limit=5` → pick the artist id. -2. **List the catalog (with album art)** — - `GET /api/spotify/artist/albums?id=&include_groups=album,single&limit=50&offset=` - → `items[]` each with `name`, `id`, `release_date`, `total_tracks`, and `images[]` - (640/300/64px — use the 300px URL for the PDF). **Page it:** the cap is 50, so walk `offset` - in steps of 50 until a short page — large catalogs run to hundreds of releases and a single - page silently truncates them. `fetch_catalog.py` does this. -3. **(optional) warm the store** — `POST /api/research/measurement-jobs` triggers a fresh live - measurement. The UI calls this first; for an already-measured artist the store already has data. -4. **Per-album play counts** — `GET /api/research/albums//measurements` → - `measurements[]` of `{isrc, spotify_track_id, name, value, captured_at, data_source}`. Sum - `value` per album for release-level streams; sum across albums for lifetime streams. -5. **Lead capture** (marketing site only) — `POST /api/valuation/lead` writes the Attio lead. - -## Why this beats scraping - -- **Album art comes free** from step 2 (`images[]`) — no DOM parsing. -- **Store-served, no 429.** Album measurements return `data_source: "apify_spotify_playcount"` — - they read Recoup's measurement store, **not** Songstats, so they don't hit the Songstats quota - that 429s the artist-name research endpoints. -- **More accurate than a UI snapshot.** The live UI can render a release as "0 streams" if its - measurement job hasn't finished yet. The store backfills within minutes — re-read and it's there. - Don't infer "dormant catalog" from a `$0` row in the UI; confirm against the measurements endpoint. - -## Eventual consistency (read this before trusting a single pass) - -Measurements are **eventually consistent**. A single read can be partial — the same artist has read -back as `0 dormant / 34 tracks` on one call and `13 dormant / 22 tracks` minutes later, purely from -in-flight measurement jobs. So: - -- `fetch_catalog.py` **retries** empty albums a few times; for a high-stakes report, run it twice and - keep the larger result, or wait a minute and re-run until track/stream counts stabilize. -- Dedupe tracks by `spotify_track_id`/`isrc` when totalling lifetime streams and track count — some - tracks are cross-listed across albums (singles that also appear on an album/compilation), so a naive - per-album sum double-counts. `fetch_catalog.py` does this. -- Optionally `POST /api/research/measurement-jobs` first to warm the store before reading. - -## Refined social metrics — exact followers + real bios via the API (no scraping, no Supabase) - -Recoup keeps a `socials` store with **`username`, `profile_url`, `follower_count`, `following_count`, -`bio`, `avatar`, `region`** — richer and **exact** vs. the rounded "1M" a public Instagram -`og:description` shows. Onboard the valued artist and pull this entirely over the API. Full flow, all -on `https://api.recoupable.com` (confirmed working with a Bearer Privy JWT, **including writes** — the -old "Privy tokens don't verify on prod" note was wrong). This is **part of working the lead**, not -overkill: it also lands the artist in the lead's own Recoup workspace so the metrics stay refreshable. - -1. **Resolve the lead's account** — `POST /api/accounts {"email": ""}`. Idempotent: if the - account already exists it is returned (with `account_id`). This is the email→`account_id` lookup — - **use this, not Supabase**. -2. **Create the artist under the lead's account** — - `POST /api/artists {"name": "", "account_id": ""}` → returns the new - **artist account id** as `artist.account_id`. Pass `account_id` only when your token's account has - org access to the lead's account; otherwise the artist is created under your own account. -3. **Attach the verified profile URLs** — - `PATCH /api/artists/{artistAccountId} {"profileUrls": {"INSTAGRAM": "...", "SPOTIFY": "...", - "YOUTUBE": "...", "FACEBOOK": "..."}}`. **Keys must be UPPERCASE** (`SPOTIFY`, `INSTAGRAM`, - `TIKTOK`, `TWITTER`, `YOUTUBE`, `APPLE`, `FACEBOOK`, `THREADS`) — lowercase silently creates - duplicate socials instead of replacing. -4. **Trigger the scrape** — one platform: `POST /api/socials/{social_id}/scrape` (get `social_id` from - `GET /api/artists/{artistAccountId}/socials`); all platforms at once: - `POST /api/artist/socials/scrape {"artist_account_id": ""}`. Each returns - `{runId, datasetId}` (Apify). -5. **Poll, then read back** — `GET /api/apify/runs/{runId}` until `status` is `SUCCEEDED`, then - `GET /api/artists/{artistAccountId}/socials` for the real `follower_count` + `bio` per platform. - `GET /api/artists?account_id=` lists artists under an account to confirm the setup. - -Caveat: this is keyed to a **platform artist account + stored social records**, not arbitrary -handles. A fresh valuation lead (e.g. an artist you only have a Spotify id for) usually has **no -socials rows yet** — you'd onboard the artist (steps above), attach the profile URLs, then scrape. -Worth it for ongoing/refreshable metrics + `region` (audience geography) and `avatar`; overkill for -a one-off report. - -Live-run caveat (Ulices Chaidez, 2026-06-22): the **Instagram** actor returned an exact 1,276,417 -(vs the rounded "1M" public meta) plus the real bio, but the Spotify/YouTube/Facebook actors returned -empty `bio`/`follower_count` on that pass. For those, fall back to the platform page — read it via the -**authenticated browser (chrome-devtools MCP)**, since LinkedIn/Instagram block `WebFetch` (HTTP 999). -Put **only real platform bios** in the lead JSON `socials[].bio` (no editorial filler; leave blank if -unverified). - -## The tool's number is often a partial run — always re-measure the full catalog - -The free valuation charges credits per measurement, so a large catalog can exhaust the lead's credits -mid-run and report a value built on only **part** of the catalog (Chilled Cat: ~27% measured, ~$290K -shown vs ~$1.08M full; Ulices Chaidez: 115.5M streams shown vs ~1.25B measured, ~11x). You don't need -to check credits to catch this — `fetch_catalog.py` already pages the **whole** catalog, so just -re-measure and compare to the tool's figure. A big gap means the run was cut short — reframe the -outreach as *"we finished your interrupted run."* - -## What the API does NOT give you - -Dollar figures (catalog value, per-release value) are a **model output**, not an API field. Take -the band the marketing tool displays, or compute it with the `catalog-value-estimator` skill -(streams → annual NLS → × multiple). `fetch_catalog.py` fills streams + art + counts and leaves -the dollar fields for you to add. - -**The estimator's value is trailing-12-month-driven and needs history.** It prices on TTM streams -derived from snapshot deltas (`/research/track/playcount-deltas`, which needs ≥2 captures ≥7 days -apart). A brand-new lead has only a single capture date → **TTM 0 → value $0** — you cannot recompute -a rigorous dollar figure same-day. Two honest workarounds: (a) scale the marketing tool's own -per-stream basis for *that* artist (`tool_value / tool_streams_measured`) to the full live catalog; -or (b) trigger snapshots now and re-run the estimator in ~4 weeks once a trailing window exists. diff --git a/skills/valuation-sales-pipeline/scripts/fetch_catalog.py b/skills/valuation-sales-pipeline/scripts/fetch_catalog.py deleted file mode 100644 index 6a9ab25..0000000 --- a/skills/valuation-sales-pipeline/scripts/fetch_catalog.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python3 -"""Assemble a lead's catalog data straight from the public Recoup APIs. - -This is the same data the marketing valuation tool uses — no DOM scraping. It -pulls the artist's albums (names, years, track counts, **album art**) and the -per-album live play counts (store-served Spotify counts — no Songstats quota, -so no 429), then writes a lead JSON ready for render_valuation_pdf.py. - -Dollar figures (est_catalog_value, per-release value) come from the valuation -model — load the catalog-value-estimator skill to fill those in, or copy the -band the marketing tool shows. This script fills everything else. - -Auth: RECOUP_API_KEY (x-api-key) or RECOUP_ACCESS_TOKEN (Bearer). -Run from the skill dir: - python3 scripts/fetch_catalog.py --artist-id 3EPASK2OUUcDo6RgfnroTK --out lead.json - python3 scripts/fetch_catalog.py --artist "ICEBOX" --out lead.json -""" -import argparse, json, os, sys, time, urllib.request, urllib.parse - -BASE = os.environ.get("RECOUP_API_BASE", "https://recoup-api.vercel.app") - - -def _headers(): - key = os.environ.get("RECOUP_API_KEY") - tok = os.environ.get("RECOUP_ACCESS_TOKEN") - if key: - return {"x-api-key": key} - if tok: - return {"Authorization": "Bearer " + tok} - sys.exit("Set RECOUP_API_KEY or RECOUP_ACCESS_TOKEN.") - - -def _get(path): - req = urllib.request.Request(BASE + path, headers=_headers()) - with urllib.request.urlopen(req, timeout=60) as r: - return json.loads(r.read().decode()) - - -def _img300(images): - for im in images or []: - if im.get("height") == 300: - return im.get("url") - return (images[0]["url"] if images else None) - - -def resolve_artist_id(name): - d = _get("/api/spotify/search?" + urllib.parse.urlencode({"q": name, "limit": 5})) - items = d.get("artists") or d.get("items") or d.get("results") or [] - if not items: - sys.exit(f"No artist found for {name!r}") - a = items[0] - return a.get("id") or a.get("spotify_artist_id"), a.get("name", name) - - -def main(): - ap = argparse.ArgumentParser() - g = ap.add_mutually_exclusive_group(required=True) - g.add_argument("--artist-id", help="Spotify artist id") - g.add_argument("--artist", help="artist name (resolved via /api/spotify/search)") - ap.add_argument("--out", required=True, help="output lead JSON path") - ap.add_argument("--name", help="override artist display name") - args = ap.parse_args() - - artist_id = args.artist_id - name = args.name - if args.artist and not artist_id: - artist_id, resolved = resolve_artist_id(args.artist) - name = name or resolved - name = name or args.artist or artist_id - - # Page the WHOLE catalog — large catalogs (lo-fi / compilation brands) routinely run to - # hundreds of releases, and a single limit=50 page silently truncates them (observed: a - # 126-release catalog read back as 50, undercounting lifetime streams by ~3x). - albums, offset = [], 0 - while True: - page = _get("/api/spotify/artist/albums?" + urllib.parse.urlencode( - {"id": artist_id, "include_groups": "album,single", "limit": 50, "offset": offset})).get("items", []) - albums += page - if len(page) < 50: - break - offset += 50 - - releases, dormant = [], 0 - seen, uni_streams, uni_tracks = set(), 0, 0 # dedupe tracks cross-listed across albums - for al in albums: - aid = al.get("id") - # Measurements are eventually consistent — an album can read empty while its - # measurement job is still running. Retry a few times so the first run is complete. - ms = [] - for attempt in range(3): - try: - ms = _get(f"/api/research/albums/{aid}/measurements").get("measurements", []) - except Exception: - ms = [] - if ms: - break - time.sleep(2) - streams = sum(int(m.get("value") or 0) for m in ms) # album-level total (for display) - for m in ms: - tid = m.get("spotify_track_id") or m.get("isrc") - if tid and tid not in seen: - seen.add(tid) - uni_streams += int(m.get("value") or 0) - uni_tracks += 1 - if streams <= 0: - dormant += 1 - releases.append({ - "name": al.get("name"), - "year": int((al.get("release_date") or "0")[:4] or 0) or None, - "tracks": len(ms), - "streams": streams, - "image": _img300(al.get("images")), - }) - - # Loud failure on the most common trap: /api/research/* rejects a hex x-api-key (it only - # works for /api/spotify/*) and rejects an expired Bearer token. fetch reads those failures - # as empty albums, so the catalog looks "0 streams / all dormant" when it's really an auth - # problem. Surface it instead of emitting a plausible-but-wrong empty catalog. - if albums and uni_streams == 0: - print("WARNING: 0 streams measured across all " + str(len(albums)) + " albums. /api/research/* " - "likely rejected the credential — use a Bearer RECOUP_ACCESS_TOKEN (not a hex " - "RECOUP_API_KEY, which only authorizes /api/spotify/*), and make sure it hasn't expired " - "(~1h for a Privy session token). This is almost always auth/expiry, not a dormant catalog.", - file=sys.stderr) - - releases.sort(key=lambda r: r.get("streams") or 0, reverse=True) - lead = { - "artist": name, - "spotify_artist_url": f"https://open.spotify.com/artist/{artist_id}", - "lifetime_streams": uni_streams, - "tracks_measured": uni_tracks, - "releases_measured": len(albums), - "dormant_releases": dormant, - "releases": releases, - "_note": "Catalog + album art from public Recoup APIs. Fill est_catalog_value / " - "value_low / value_high / per-release value via the catalog-value-estimator " - "model (or copy the band the marketing valuation tool shows).", - } - with open(args.out, "w") as f: - json.dump(lead, f, indent=2) - print(f"Wrote {args.out}: {len(albums)} releases, {uni_tracks} tracks, " - f"{uni_streams:,} streams, {dormant} dormant") - - -if __name__ == "__main__": - main() diff --git a/skills/valuation-sales-pipeline/scripts/render_valuation_pdf.py b/skills/valuation-sales-pipeline/scripts/render_valuation_pdf.py deleted file mode 100644 index a4c3d64..0000000 --- a/skills/valuation-sales-pipeline/scripts/render_valuation_pdf.py +++ /dev/null @@ -1,405 +0,0 @@ -#!/usr/bin/env python3 -"""Render a clean catalog-valuation PDF for a sales lead. - -Reads a lead JSON (see fixtures/example-lead.json for the shape) and writes: - - Page 1 - the headline the lead already saw: estimated value + range, key stats, - the top-releases breakdown with album art ("what we measured"), and the - artist's connected socials. - Page 2 - an honest reading of THAT data (concentration, dormant tail, range - caveats) - grounded in page 1, no unverified claims. - Appendix A - the full measured catalog (every release with art). - Optional - if the lead JSON has a `roster` block, a roster CTA page + an - "Appendix B - full roster" (used for label/manager leads). - -Lead data can be assembled directly from the public Recoup APIs with -scripts/fetch_catalog.py (album art + streams), so no DOM scraping is needed. - -Run it from the skill dir: - python3 scripts/render_valuation_pdf.py --lead fixtures/example-lead.json --out ./out - -Requires reportlab: pip install reportlab --break-system-packages -""" -import argparse, json, os, sys, urllib.request -from io import BytesIO - - -def money(n): - try: - return "${:,.0f}".format(float(n)) - except (TypeError, ValueError): - return str(n or "-") - - -def hstreams(n): - try: - n = float(n) - except (TypeError, ValueError): - return "-" - if n >= 1_000_000: - return "{:.1f}M".format(n / 1_000_000) - if n >= 1_000: - return "{:.0f}K".format(n / 1_000) - return "{:.0f}".format(n) - - -def thousands(n): - try: - return "{:,.0f}".format(float(n)) - except (TypeError, ValueError): - return "-" - - -def build(lead, out_dir): - try: - from reportlab.lib.pagesizes import LETTER - from reportlab.lib.units import inch - from reportlab.lib import colors - from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle - from reportlab.platypus import ( - SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image, PageBreak, CondPageBreak, - KeepTogether, - ) - except ImportError: - sys.exit("reportlab not installed. Run: pip install reportlab --break-system-packages") - - def cover(url, pt=34): - """Download an album-art URL into a small reportlab Image (or '' on failure).""" - if not url: - return "" - try: - req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) - with urllib.request.urlopen(req, timeout=15) as r: - img = Image(BytesIO(r.read())) - img.drawWidth = pt - img.drawHeight = pt - return img - except Exception: - return "" - - artist = lead.get("artist") or "Unknown Artist" - central = lead.get("est_catalog_value") - low = lead.get("value_low") - high = lead.get("value_high") - - os.makedirs(out_dir, exist_ok=True) - safe = "".join(c if c.isalnum() else "-" for c in artist).strip("-") or "artist" - path = os.path.join(out_dir, f"{safe}-valuation.pdf") - - styles = getSampleStyleSheet() - navy = colors.HexColor("#0b1f3a") - h1 = ParagraphStyle("h1", parent=styles["Title"], textColor=navy, fontSize=24, spaceAfter=2) - h2 = ParagraphStyle("h2", parent=styles["Heading2"], textColor=navy, fontSize=16, spaceAfter=8) - sub = ParagraphStyle("sub", parent=styles["Normal"], textColor=colors.grey, fontSize=11, spaceAfter=14) - body = ParagraphStyle("body", parent=styles["Normal"], fontSize=10.5, leading=15, spaceAfter=8) - small = ParagraphStyle("small", parent=styles["Normal"], fontSize=9, leading=11) - cellbw = ParagraphStyle("cellbw", parent=styles["Normal"], textColor=colors.white, fontSize=10, leading=13) - big = ParagraphStyle("big", parent=styles["Normal"], textColor=colors.white, fontSize=22, leading=24) - - doc = SimpleDocTemplate(path, pagesize=LETTER, topMargin=0.7 * inch, - bottomMargin=0.7 * inch, leftMargin=0.8 * inch, rightMargin=0.8 * inch) - story = [] - story.append(Paragraph(f"{artist} - Catalog Valuation", h1)) - story.append(Paragraph("Recoup · measured live from public streaming data", sub)) - - # Admin reference (owner email + account ids) - for easy lookup by admins - acct = lead.get("account") or {} - ref = [] - if acct.get("owner_email"): - ref.append("owner " + acct["owner_email"]) - if acct.get("owner_account_id"): - ref.append("account " + acct["owner_account_id"]) - if ref: - story.append(Paragraph("Admin reference - " + " · ".join(ref), - ParagraphStyle("admin", parent=styles["Normal"], fontSize=8, - textColor=colors.grey, spaceAfter=10))) - - # Headline value band - band = Table([[Paragraph("Estimated catalog value", cellbw)], - [Paragraph(money(central), big)], - [Paragraph(f"Range {money(low)} - {money(high)}" if low and high else "", cellbw)]], - colWidths=[6.9 * inch]) - band.setStyle(TableStyle([ - ("BACKGROUND", (0, 0), (-1, -1), navy), - ("LEFTPADDING", (0, 0), (-1, -1), 16), ("RIGHTPADDING", (0, 0), (-1, -1), 16), - ("TOPPADDING", (0, 0), (-1, 0), 10), ("BOTTOMPADDING", (0, -1), (-1, -1), 12), - ])) - story.append(band) - story.append(Spacer(1, 16)) - - # Key stats (only rows that are present) - rows = [["Lifetime streams", hstreams(lead.get("lifetime_streams"))], - ["Followers", thousands(lead.get("follower_count"))]] - if lead.get("tracks_measured") is not None: - rows.append(["Tracks measured", thousands(lead.get("tracks_measured"))]) - if lead.get("releases_measured") is not None: - rows.append(["Releases measured", thousands(lead.get("releases_measured"))]) - rows += [["Relationship", lead.get("relationship") or "-"], - ["Valued", lead.get("valued_at") or "-"]] - stats = Table(rows, colWidths=[2.3 * inch, 4.6 * inch]) - stats.setStyle(TableStyle([ - ("FONTSIZE", (0, 0), (-1, -1), 10.5), - ("TEXTCOLOR", (0, 0), (0, -1), navy), - ("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"), - ("LINEBELOW", (0, 0), (-1, -2), 0.4, colors.HexColor("#dddddd")), - ("TOPPADDING", (0, 0), (-1, -1), 6), ("BOTTOMPADDING", (0, 0), (-1, -1), 6), - ])) - story.append(stats) - story.append(Spacer(1, 16)) - - # Per-release breakdown ("what we measured") with album art. - # Include the Value column only if any release carries a dollar value. - releases = lead.get("releases") or [] - has_value = any(r.get("value") is not None for r in releases) - if releases: - story.append(Paragraph("What we measured", body)) - cols = ["", "Release", "Year", "Tracks"] + (["Value"] if has_value else []) + ["Streams"] - head = [Paragraph(t, cellbw) for t in cols] - data = [head] - shown = releases[:7] - for r in shown: - row = [cover(r.get("image"), 30), (r.get("name") or "-")[:34], - str(r.get("year") or "-"), - str(r.get("tracks") if r.get("tracks") is not None else "-")] - if has_value: - row.append(money(r.get("value")) if r.get("value") is not None else "-") - row.append(hstreams(r.get("streams"))) - data.append(row) - total_rel = lead.get("releases_measured") or len(releases) - if total_rel > len(shown): - data.append(["", f"+ {total_rel - len(shown)} more releases (long tail)"] + [""] * (len(cols) - 2)) - widths = [0.55 * inch, 2.45 * inch, 0.6 * inch, 0.7 * inch] + ([1.0 * inch] if has_value else []) + [0.9 * inch] - tbl = Table(data, colWidths=widths) - tbl.setStyle(TableStyle([ - ("BACKGROUND", (0, 0), (-1, 0), navy), - ("FONTSIZE", (0, 1), (-1, -1), 9.5), - ("ALIGN", (2, 0), (-1, -1), "RIGHT"), - ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), - ("LINEBELOW", (0, 1), (-1, -2), 0.3, colors.HexColor("#eeeeee")), - ("TOPPADDING", (0, 0), (-1, -1), 4), ("BOTTOMPADDING", (0, 0), (-1, -1), 4), - ])) - story.append(tbl) - story.append(Spacer(1, 6)) - story.append(Paragraph( - "Top releases by streams; full catalog in the appendix. Measured live from public " - "Spotify play counts.", - ParagraphStyle("note", parent=styles["Normal"], fontSize=8.5, textColor=colors.grey))) - - # Artist channels - verified socials table (last block on page 1) - socials = lead.get("socials") - if isinstance(socials, list) and socials: - shead = [Paragraph(t, cellbw) for t in ("Platform", "Handle", "Followers", "Bio")] - sdata = [shead] - for s in socials: - handle = s.get("handle") or s.get("platform") or "-" - url = s.get("url") - hcell = Paragraph( - f'{handle}' if url else handle, small) - # Cap the bio so one verbose profile (e.g. a long quote) can't inflate the table - # height and bump the "Reading your result" section onto its own near-empty page. - bio = s.get("bio") or "-" - if len(bio) > 88: - bio = bio[:87].rstrip() + "…" - sdata.append([s.get("platform") or "-", hcell, thousands(s.get("followers")), - Paragraph(bio, small)]) - stbl = Table(sdata, colWidths=[1.1 * inch, 1.65 * inch, 0.85 * inch, 3.0 * inch]) - stbl.setStyle(TableStyle([ - ("BACKGROUND", (0, 0), (-1, 0), navy), - ("FONTSIZE", (0, 1), (-1, -1), 9), - ("ALIGN", (2, 1), (2, -1), "RIGHT"), - ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), - ("LINEBELOW", (0, 1), (-1, -2), 0.3, colors.HexColor("#eeeeee")), - ("TOPPADDING", (0, 0), (-1, -1), 5), ("BOTTOMPADDING", (0, 0), (-1, -1), 5), - ])) - # KeepTogether so the heading + table never split across a page boundary. - story.append(Spacer(1, 12)) - story.append(KeepTogether([Paragraph("Artist channels", body), Spacer(1, 4), stbl])) - - # ---- Page 2: an honest reading of THIS catalog's data ---- - # CondPageBreak (not a hard PageBreak) avoids a blank page when page 1 is already full. - story.append(CondPageBreak(6.5 * inch)) - story.append(Paragraph("Reading your result", h2)) - story.append(Paragraph( - f"Everything on the previous page is measured live from public play counts - the same " - f"figures you saw when you ran {artist}. A few honest reads from that data:", body)) - - # Concentration - by value if present, else by streams. - try: - if releases: - key = "value" if has_value else "streams" - top = max(releases, key=lambda r: float(r.get(key) or 0)) - tv = float(top.get(key) or 0) - if has_value and central: - denom = float(central) - elif not has_value and lead.get("lifetime_streams"): - denom = float(lead.get("lifetime_streams")) - else: - denom = sum(float(r.get(key) or 0) for r in releases) - pct = round(100 * tv / denom) if denom else 0 - unit = "estimated value" if has_value else "streams" - if pct >= 40: - story.append(Paragraph( - f"It rests on one release. ~{pct}% of the catalog's {unit} comes from " - f"“{top.get('name')}.” That makes the value real but concentrated - a catalog " - f"spread across more songs is steadier to own and tends to be valued higher.", body)) - except (TypeError, ValueError): - pass - - # Analyst-supplied verified observations (case-specific, e.g. collab splits, audience gap) - for note in (lead.get("reading_notes") or []): - story.append(Paragraph(note, body)) - - # Dormant tail - dormant = lead.get("dormant_releases") - rm = lead.get("releases_measured") - if dormant and rm: - story.append(Paragraph( - f"{dormant} of {rm} releases show little or no measurable public streaming. " - f"Some may be genuinely dormant; others may simply not be surfacing the way they should. " - f"Either way, it's worth knowing which is which.", body)) - - # Range honesty - if low and high: - story.append(Paragraph( - f"The band is wide ({money(low)}-{money(high)}) on purpose. It's modeled from " - f"public streaming alone and is master-side only - publishing isn't included. A real " - f"royalty statement would narrow it considerably.", body)) - - # Sincere close - story.append(Spacer(1, 4)) - story.append(Paragraph( - f"We built this to be accurate to what's public, not to flatter. If it matches what you already " - f"know about {artist}, the honest next step is to read it against your actual statements - " - f"that's where the range tightens and anything uncollected shows up.", body)) - - # How this is calculated - stays on page 2 with the reading. - story.append(Spacer(1, 6)) - story.append(Paragraph("How this is calculated", body)) - story.append(Paragraph( - "Value is modeled as sustainable annual Net Label Share (NLS) × a 10-16× market multiple - " - "what the owner keeps after artist royalties, distribution, and reserves, not headline revenue. " - "Other platforms approximated as a labeled share of Spotify. Directional estimate, not an appraisal.", body)) - story.append(Spacer(1, 6)) - story.append(Paragraph( - "Prepared by Recoup. Estimate only - not financial advice or an offer.", - ParagraphStyle("foot", parent=styles["Normal"], fontSize=8.5, textColor=colors.grey))) - - # Roster CTA - on its own page (hard PageBreak before; appendix breaks after). - roster = lead.get("roster") or {} - rartists = roster.get("artists") or [] - if rartists: - label = (roster.get("label") or "").strip() - cta_block = [Paragraph(f"Extend the analysis - the {label} roster".strip(), h2)] - if roster.get("note"): - cta_block.append(Paragraph(roster["note"], body)) - rdata = [[Paragraph(t, cellbw) for t in ("Artist", "Spotify followers")]] - for a in rartists: - rdata.append([a.get("name", "-"), thousands(a.get("followers"))]) - if roster.get("more_count"): - rdata.append([Paragraph(f"+ {roster['more_count']} more on the roster", small), ""]) - rtbl = Table(rdata, colWidths=[3.4 * inch, 1.6 * inch]) - rtbl.setStyle(TableStyle([ - ("BACKGROUND", (0, 0), (-1, 0), navy), - ("FONTSIZE", (0, 1), (-1, -1), 9.5), - ("ALIGN", (1, 1), (1, -1), "RIGHT"), - ("LINEBELOW", (0, 1), (-1, -2), 0.3, colors.HexColor("#eeeeee")), - ("TOPPADDING", (0, 0), (-1, -1), 4), ("BOTTOMPADDING", (0, 0), (-1, -1), 4), - ])) - cta_block += [Spacer(1, 6), rtbl] - if roster.get("cta"): - cta_block += [Spacer(1, 6), Paragraph("" + roster["cta"] + "", body)] - story.append(PageBreak()) - story.append(KeepTogether(cta_block)) - - # ---- Appendix: the full catalog measured (matches the live tool's full list) ---- - if releases: - story.append(PageBreak()) - total_rel = lead.get("releases_measured") or len(releases) - story.append(Paragraph("Appendix A - full catalog measured", h2)) - story.append(Paragraph( - f"All {total_rel} releases measured live from public Spotify play counts. Streams are " - f"platform-displayed counts; per-release value is modeled at the catalog level (see page 2). " - f"Page 1 shows the top releases by streams; the complete set is below.", body)) - ahead = [Paragraph(t, cellbw) for t in ("#", "", "Release", "Year", "Tracks", "Streams")] - adata = [ahead] - for i, r in enumerate(releases, 1): - adata.append([str(i), cover(r.get("image"), 22), (r.get("name") or "-")[:46], - str(r.get("year") or "-"), - str(r.get("tracks") if r.get("tracks") is not None else "-"), - hstreams(r.get("streams"))]) - atbl = Table(adata, colWidths=[0.3 * inch, 0.4 * inch, 3.25 * inch, 0.6 * inch, 0.7 * inch, 1.05 * inch], repeatRows=1) - atbl.setStyle(TableStyle([ - ("BACKGROUND", (0, 0), (-1, 0), navy), - ("FONTSIZE", (0, 1), (-1, -1), 9), - ("ALIGN", (0, 0), (0, -1), "RIGHT"), - ("ALIGN", (3, 0), (-1, -1), "RIGHT"), - ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), - ("LINEBELOW", (0, 1), (-1, -2), 0.25, colors.HexColor("#eeeeee")), - ("TOPPADDING", (0, 0), (-1, -1), 3), ("BOTTOMPADDING", (0, 0), (-1, -1), 3), - ])) - story.append(atbl) - - # ---- Appendix B: the full label roster (pulled from the label site) ---- - full = (lead.get("roster") or {}).get("full") or [] - if full: - rlabel = ((lead.get("roster") or {}).get("label") or "label").strip() - rsource = (lead.get("roster") or {}).get("source") - story.append(PageBreak()) - story.append(Paragraph(f"Appendix B - full {rlabel} roster", h2)) - src = f" ({rsource})" if rsource else "" - any_missing = any(isinstance(a, dict) and a.get("followers") is None for a in full) - caveat = (" Spotify follower counts as provided in the lead data (- = not provided)." - if any_missing else " Spotify follower counts as provided in the lead data.") - story.append(Paragraph( - f"The complete {rlabel} roster - {len(full)} artists{src}. The same valuation + socials " - f"baseline runs on any of them.{caveat}", body)) - story.append(Spacer(1, 6)) - if full and isinstance(full[0], dict): - # Artist | Spotify followers, sorted desc (unresolved last) - rows = sorted(full, key=lambda x: (x.get("followers") is None, -(x.get("followers") or 0))) - bdata = [[Paragraph(t, cellbw) for t in ("Artist", "Spotify followers")]] - for a in rows: - f = a.get("followers") - bdata.append([a.get("name", "-"), thousands(f) if f is not None else "-"]) - btbl = Table(bdata, colWidths=[3.7 * inch, 1.6 * inch], repeatRows=1) - btbl.setStyle(TableStyle([ - ("BACKGROUND", (0, 0), (-1, 0), navy), - ("FONTSIZE", (0, 1), (-1, -1), 9.5), - ("ALIGN", (1, 1), (1, -1), "RIGHT"), - ("LINEBELOW", (0, 1), (-1, -2), 0.25, colors.HexColor("#eeeeee")), - ("TOPPADDING", (0, 0), (-1, -1), 3), ("BOTTOMPADDING", (0, 0), (-1, -1), 3), - ])) - story.append(btbl) - else: - ncols = 3 - per = -(-len(full) // ncols) # ceil - cols = [full[i * per:(i + 1) * per] for i in range(ncols)] - nrows = max(len(c) for c in cols) - grid = [] - for i in range(nrows): - grid.append([(f"{i + 1 + j * per}. {cols[j][i]}" if i < len(cols[j]) else "") for j in range(ncols)]) - gtbl = Table(grid, colWidths=[2.3 * inch] * ncols) - gtbl.setStyle(TableStyle([ - ("FONTSIZE", (0, 0), (-1, -1), 9.5), - ("TOPPADDING", (0, 0), (-1, -1), 4), ("BOTTOMPADDING", (0, 0), (-1, -1), 4), - ("VALIGN", (0, 0), (-1, -1), "TOP"), - ])) - story.append(gtbl) - - doc.build(story) - return path - - -def main(): - ap = argparse.ArgumentParser() - ap.add_argument("--lead", required=True, help="path to lead JSON") - ap.add_argument("--out", default="./out", help="output directory") - args = ap.parse_args() - with open(args.lead) as f: - lead = json.load(f) - path = build(lead, args.out) - print(f"Wrote {path}") - - -if __name__ == "__main__": - main() diff --git a/skills/valuation-sales-pipeline/templates/outreach-email.md b/skills/valuation-sales-pipeline/templates/outreach-email.md deleted file mode 100644 index 33d54dc..0000000 --- a/skills/valuation-sales-pipeline/templates/outreach-email.md +++ /dev/null @@ -1,46 +0,0 @@ -# First outreach email - template - -First touch is **scannable in 3-5 seconds** - a busy inbox gives you a glance, not a read. Keep it -to **2 sentences + 3 bullets + 1 CTA**. From the **rep personally**; reference the **specific artist -+ their number**; **deliver the valuation PDF** as the long-form next step (give before you ask); -plain hyphens only (no em/en dashes, which read as AI-generated). Put the depth in the PDF, not the -email - the PDF is there for anyone who wants the data before taking the CTA. - -## Reusable template - -> **Subject:** {Artist} - your catalog valuation ({hook}) -> -> Hi {first_name}, your {Artist} valuation {one-line context}. I finished the run - full breakdown -> attached, and the headline is a lot bigger: [attach: {Artist}-valuation.pdf] -> -> - {bullet 1 - the corrected headline: full lifetime streams vs. what the tool measured} -> - {bullet 2 - corrected value vs. the figure the tool showed} -> - {bullet 3 - one free insight: concentration, uncollected royalty source, master-vs-publishing} -> -> Worth 15 minutes this week to walk through it? [calendar link] -> -> - {rep_name} - -Three short bullets, max one line each. If a point needs a sentence of caveat, it belongs in the -PDF, not here. Don't pitch the engagement in the cold email - that's the call (see Lever below). - -## Variables - -| Variable | Source | -| --- | --- | -| `{Artist}` | person record `looked_up_artist` | -| `{first_name}` | person record `name` (or "there") | -| `{hook}` | the one-glance reason to open - e.g. "your valuation was cut short (real number is ~10x higher)" | -| `{one-line context}` | the setup the bullets pay off - e.g. "stopped partway and priced it at {tool_value} off ~9% of the catalog" | -| `{bullet 1-3}` | from the lead JSON / PDF: corrected streams, corrected value vs. tool, one insight | -| `{rep_name}` | the Attio `owner` working the lead | -| `[calendar link]` | the rep's booking link | - -## Lever (for the follow-up call, not the cold email) - -Keep the cold email about *their* number + the CTA. Save the engagement framing for the call, and -tailor it to the relationship: - -- **Owner/Operator** -> catalog growth + recovery on their own catalog. Strongest pilot fit. -- **Label / Manager / distributor** -> buyer-ready baseline + measurement/recovery across the whole roster. -- **Collaborator** -> artist-to-artist; deliver the baseline, confirm their role, widen later. diff --git a/plugins/recoup-records/templates/deal-workspace/assumptions.yaml b/templates/deal-workspace/assumptions.yaml similarity index 100% rename from plugins/recoup-records/templates/deal-workspace/assumptions.yaml rename to templates/deal-workspace/assumptions.yaml diff --git a/plugins/recoup-records/templates/deal-workspace/evidence-ledger.json b/templates/deal-workspace/evidence-ledger.json similarity index 100% rename from plugins/recoup-records/templates/deal-workspace/evidence-ledger.json rename to templates/deal-workspace/evidence-ledger.json diff --git a/plugins/recoup-records/templates/deal-workspace/findings.json b/templates/deal-workspace/findings.json similarity index 100% rename from plugins/recoup-records/templates/deal-workspace/findings.json rename to templates/deal-workspace/findings.json diff --git a/plugins/recoup-records/templates/deal-workspace/memos/financing-pack.md b/templates/deal-workspace/memos/financing-pack.md similarity index 100% rename from plugins/recoup-records/templates/deal-workspace/memos/financing-pack.md rename to templates/deal-workspace/memos/financing-pack.md diff --git a/plugins/recoup-records/templates/deal-workspace/memos/ic-memo.md b/templates/deal-workspace/memos/ic-memo.md similarity index 100% rename from plugins/recoup-records/templates/deal-workspace/memos/ic-memo.md rename to templates/deal-workspace/memos/ic-memo.md diff --git a/plugins/recoup-records/templates/deal-workspace/memos/seller-cleanup-report.md b/templates/deal-workspace/memos/seller-cleanup-report.md similarity index 100% rename from plugins/recoup-records/templates/deal-workspace/memos/seller-cleanup-report.md rename to templates/deal-workspace/memos/seller-cleanup-report.md diff --git a/plugins/recoup-records/templates/deal-workspace/missing-files.md b/templates/deal-workspace/missing-files.md similarity index 100% rename from plugins/recoup-records/templates/deal-workspace/missing-files.md rename to templates/deal-workspace/missing-files.md From b4a2711149f3a4b97d3b32915e4dc8f6e20072f0 Mon Sep 17 00:00:00 2001 From: Sidney Swift <158200036+sidneyswift@users.noreply.github.com> Date: Tue, 23 Jun 2026 02:37:17 -0400 Subject: [PATCH 2/5] =?UTF-8?q?refactor(branding):=20author=20=E2=86=92=20?= =?UTF-8?q?Recoup,=20support@=20email,=20compelling=20descriptions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Author/owner/developer name "Recoupable" → "Recoup" across all four manifests and prose (README, AGENTS.md, internal skill bodies). Domain (recoupable.com) and GitHub slug (recoupable/skills) intentionally unchanged. - All author/owner emails agent@recoupable.com → support@recoupable.com (manifests + README support line + the AGENTS.md / docs convention notes). - Rewrote the plugin + interface descriptions as compelling, customer-facing copy; dropped non-customer detail (internal-tooling mention, npx/portability mechanics). Co-Authored-By: Claude Opus 4.8 --- .agents/plugins/marketplace.json | 14 +++++++------- .claude-plugin/marketplace.json | 8 ++++---- .claude-plugin/plugin.json | 6 +++--- .codex-plugin/plugin.json | 19 +++++++++++-------- AGENTS.md | 2 +- README.md | 4 ++-- docs/TEMPLATE.md | 4 ++-- .../SKILL.md | 4 ++-- .../recoup-internal-dev-ship-issue/SKILL.md | 8 ++++---- skills/recoup-platform-api-access/SKILL.md | 4 ++-- 10 files changed, 38 insertions(+), 35 deletions(-) diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index 7f95170..715482e 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -2,7 +2,7 @@ "name": "recoup", "owner": { "name": "Recoup", - "email": "agent@recoupable.com", + "email": "support@recoupable.com", "url": "https://recoupable.com" }, "plugins": [ @@ -11,10 +11,10 @@ "displayName": "Recoup Skills", "source": ".", "version": "2.0.0", - "description": "Every Recoup skill in one install — a record label in a box: artist setup & API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release campaigns, plus Recoup's internal engineering & ops tooling. Portable across agents; also installable via `npx skills add recoupable/skills`.", + "description": "A record label in a box. Recoup turns your AI agent into a full music team — artist research and analytics, content and short-form video, song analysis, release campaigns, and catalog valuation — all in one install.", "author": { - "name": "Recoupable", - "email": "agent@recoupable.com", + "name": "Recoup", + "email": "support@recoupable.com", "url": "https://recoupable.com" }, "homepage": "https://recoupable.com", @@ -36,9 +36,9 @@ ], "interface": { "displayName": "Recoup Skills", - "shortDescription": "A record label in a box — every Recoup skill in one install", - "longDescription": "The whole Recoup platform as a single plugin: artist setup & API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release campaigns — every skill, agent, and hook in one bundle. Also ships Recoup's internal engineering & ops skills (gated behind the recoup-internal trigger keyword). General skills work for anyone; platform skills integrate with the Recoupable platform.", - "developerName": "Recoupable", + "shortDescription": "A record label in a box — your AI music team in one install", + "longDescription": "The whole Recoup platform in one plugin: artist research and analytics, A&R and contact discovery, content and short-form video, song analysis, end-to-end release campaigns, and catalog valuation — everything your AI music team needs to run an artist's career.", + "developerName": "Recoup", "category": "Music", "capabilities": [ "Read", diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 7d18ed1..44e1a47 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -2,7 +2,7 @@ "name": "recoup", "owner": { "name": "Recoup", - "email": "agent@recoupable.com", + "email": "support@recoupable.com", "url": "https://recoupable.com" }, "plugins": [ @@ -10,11 +10,11 @@ "name": "recoup-skills", "displayName": "Recoup Skills", "source": ".", - "description": "Every Recoup skill in one install — a record label in a box: artist setup & API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release campaigns, plus Recoup's internal engineering & ops tooling. Portable across agents; also installable via `npx skills add recoupable/skills`.", + "description": "A record label in a box. Recoup turns your AI agent into a full music team — artist research and analytics, content and short-form video, song analysis, release campaigns, and catalog valuation — all in one install.", "version": "2.0.0", "author": { - "name": "Recoupable", - "email": "agent@recoupable.com", + "name": "Recoup", + "email": "support@recoupable.com", "url": "https://recoupable.com" }, "homepage": "https://recoupable.com", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 2a32147..3de906e 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -2,10 +2,10 @@ "name": "recoup-skills", "displayName": "Recoup Skills", "version": "2.0.0", - "description": "Every Recoup skill in one install — a record label in a box: artist setup & API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release campaigns, plus Recoup's internal engineering & ops tooling. Portable across agents; also installable via `npx skills add recoupable/skills`.", + "description": "A record label in a box. Recoup turns your AI agent into a full music team — artist research and analytics, content and short-form video, song analysis, release campaigns, and catalog valuation — all in one install.", "author": { - "name": "Recoupable", - "email": "agent@recoupable.com", + "name": "Recoup", + "email": "support@recoupable.com", "url": "https://recoupable.com" }, "homepage": "https://recoupable.com", diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 10ed4ae..7a5d228 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,10 +1,10 @@ { "name": "recoup-skills", "version": "2.0.0", - "description": "Every Recoup skill in one install — a record label in a box: artist setup & API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release campaigns, plus Recoup's internal engineering & ops tooling. Portable across agents; also installable via `npx skills add recoupable/skills`.", + "description": "A record label in a box. Recoup turns your AI agent into a full music team — artist research and analytics, content and short-form video, song analysis, release campaigns, and catalog valuation — all in one install.", "author": { - "name": "Recoupable", - "email": "agent@recoupable.com", + "name": "Recoup", + "email": "support@recoupable.com", "url": "https://recoupable.com" }, "homepage": "https://recoupable.com", @@ -27,11 +27,14 @@ "skills": "./skills/", "interface": { "displayName": "Recoup Skills", - "shortDescription": "A record label in a box — every Recoup skill in one install", - "longDescription": "The whole Recoup platform as a single plugin: artist setup & API access, music-industry research, catalog deal review, content creation, song audio analysis, and end-to-end release campaigns — every skill, agent, and hook in one bundle. Also ships Recoup's internal engineering & ops skills (gated behind the recoup-internal trigger keyword). General skills work for anyone; platform skills integrate with the Recoupable platform.", - "developerName": "Recoupable", - "category": "Productivity", - "capabilities": ["Read", "Write"], + "shortDescription": "A record label in a box — your AI music team in one install", + "longDescription": "The whole Recoup platform in one plugin: artist research and analytics, A&R and contact discovery, content and short-form video, song analysis, end-to-end release campaigns, and catalog valuation — everything your AI music team needs to run an artist's career.", + "developerName": "Recoup", + "category": "Music", + "capabilities": [ + "Read", + "Write" + ], "websiteURL": "https://recoupable.com", "brandColor": "#345A5D", "composerIcon": "./assets/recoup-icon.png", diff --git a/AGENTS.md b/AGENTS.md index f66c11d..e31ccca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -137,7 +137,7 @@ The single plugin (`recoup-skills`, `source "."`) is listed in **two files that `scripts/validate_manifests.py` enforces this "dual-manifest parity" on `(name, source, version)` — **edit one, edit the other**. Keep the version in both marketplaces and both `plugin.json` files in sync. -**Author email must match across layers:** the marketplace entry's email must equal the email in `plugin.json`. Use `agent@recoupable.com` everywhere (the support email documented in `CLAUDE.md`). +**Author email must match across layers:** the marketplace entry's email must equal the email in `plugin.json`. Use `support@recoupable.com` everywhere (the support email documented in `CLAUDE.md`). ## Naming & branding diff --git a/README.md b/README.md index d5d0f83..c4c30c9 100644 --- a/README.md +++ b/README.md @@ -136,8 +136,8 @@ Add a route for the new skill in [`RESOLVER.md`](RESOLVER.md) and a fixture in ` ## About -[Recoupable](https://recoupable.com) is an AI-powered music management platform. These skills power the agents that help artists and labels manage their careers. +[Recoup](https://recoupable.com) is an AI-powered music management platform. These skills power the agents that help artists and labels manage their careers. - **Website**: [recoupable.com](https://recoupable.com) - **App**: [chat.recoupable.com](https://chat.recoupable.com) -- **Support**: agent@recoupable.com +- **Support**: support@recoupable.com diff --git a/docs/TEMPLATE.md b/docs/TEMPLATE.md index 1ad4ff5..e77353d 100644 --- a/docs/TEMPLATE.md +++ b/docs/TEMPLATE.md @@ -90,14 +90,14 @@ Accept reasonable defaults; confirm the derived names once before writing files. | Input | Example | Used for | |---|---|---| | **Track** | `public` / `private` | Picks the knob set in §1 + §19 | -| **Company legal/parent name** | `Recoupable` | `author.name`, attribution, owner object — stays the real company name | +| **Company legal/parent name** | `Recoup` | `author.name`, attribution, owner object — stays the real company name | | **Brand name** (may equal company) | `Recoup` | User-facing display labels (`Recoup — Research`) | | **Brand prefix** (kebab, lowercase) | `recoup` | Plugin + plugin-skill identifiers | | **GitHub org** | `recoupable` | Plugin source URLs, repo links (lowercase) | | **GitHub repo name** | `skills` | `https://github.com//.git` | | **License** | `Apache-2.0` (public) / `Proprietary` (private) | `LICENSE`, every manifest `license` field | | **Maintainer name / email / GitHub handle** | `Sidney Swift` / `agent@company.com` / `sidneyswift` | Owner objects, CODEOWNERS, READMEs | -| **Support email** | `agent@recoupable.com` | `owner.email`, `author.email`, attribution — **must match across every layer** | +| **Support email** | `support@recoupable.com` | `owner.email`, `author.email`, attribution — **must match across every layer** | | **Departments / teams** | Research, Deals, Content, Songs | Become plugins (one per audience cluster) | | **Known plugin skills per dept** (optional) | "artist research", "royalty normalization" | Become prefixed `skills/` folders inside a plugin | | **Known standalone skills** (optional, public) | "songwriting", "getting started" | Become unprefixed top-level `skills/` folders | diff --git a/skills/recoup-internal-dev-issue-tracker/SKILL.md b/skills/recoup-internal-dev-issue-tracker/SKILL.md index 839b1bb..95e3079 100644 --- a/skills/recoup-internal-dev-issue-tracker/SKILL.md +++ b/skills/recoup-internal-dev-issue-tracker/SKILL.md @@ -1,11 +1,11 @@ --- name: recoup-internal-dev-issue-tracker -description: INTERNAL — Recoup staff tooling, gated by the recoup-internal keyword. Invoke ONLY when the request explicitly includes "recoup-internal" (e.g. "recoup-internal write a tracking issue"). Never use for customer-facing or artist requests. Write and maintain high-signal GitHub issues — especially long-lived tracking issues that coordinate multi-PR, multi-repo efforts — in the Recoupable house style. Use when the user says "write an issue", "create a tracking issue", "open an issue for this work", "update the issue", "check off / close out an item", "log what shipped on the issue", "maintain the issue", or when capturing a plan, cutover, migration, or cleanup that spans more than one PR. Covers structure, the Open→Done lifecycle, evidence/linking rules, and acceptance criteria. +description: INTERNAL — Recoup staff tooling, gated by the recoup-internal keyword. Invoke ONLY when the request explicitly includes "recoup-internal" (e.g. "recoup-internal write a tracking issue"). Never use for customer-facing or artist requests. Write and maintain high-signal GitHub issues — especially long-lived tracking issues that coordinate multi-PR, multi-repo efforts — in the Recoup house style. Use when the user says "write an issue", "create a tracking issue", "open an issue for this work", "update the issue", "check off / close out an item", "log what shipped on the issue", "maintain the issue", or when capturing a plan, cutover, migration, or cleanup that spans more than one PR. Covers structure, the Open→Done lifecycle, evidence/linking rules, and acceptance criteria. --- # Writing & Maintaining Issues -How we write GitHub issues at Recoupable. The bar: **a teammate (or a cold agent) can read the issue and know exactly what's done, what's left, why each decision was made, and how to verify any claim — without re-deriving it from git history.** Issues are persistent memory, not a TODO dump. +How we write GitHub issues at Recoup. The bar: **a teammate (or a cold agent) can read the issue and know exactly what's done, what's left, why each decision was made, and how to verify any claim — without re-deriving it from git history.** Issues are persistent memory, not a TODO dump. Most substantial work gets a **tracking issue**: a long-lived issue that coordinates a multi-PR (often multi-submodule) effort. The two reference issues this skill is built from are [chat#1747](https://github.com/recoupable/chat/issues/1747) (cutover plan) and [chat#1767](https://github.com/recoupable/chat/issues/1767) (post-cutover cleanup). Read them if you want the gold standard. diff --git a/skills/recoup-internal-dev-ship-issue/SKILL.md b/skills/recoup-internal-dev-ship-issue/SKILL.md index 3c01e23..33f11a2 100644 --- a/skills/recoup-internal-dev-ship-issue/SKILL.md +++ b/skills/recoup-internal-dev-ship-issue/SKILL.md @@ -1,11 +1,11 @@ --- name: recoup-internal-dev-ship-issue -description: INTERNAL — Recoup staff tooling, gated by the recoup-internal keyword. Invoke ONLY when the request explicitly includes "recoup-internal" (e.g. "recoup-internal ship issue #N"). Never use for customer-facing or artist requests. Deliver a tracked GitHub issue end-to-end in the Recoupable house style — documentation-driven (OpenAPI/contract first), then test-driven (red→green) implementation that matches the docs, verified against the live preview deployment, with results posted to the PR. Use when the user says "implement this issue", "build out issue #N", "ship the issue", "do the implementation", "take this issue and build it", or hands you a tracking issue (usually from the recoup-internal-dev-issue-tracker skill) to deliver. Covers docs-first ordering, the TDD loop, preview verification, the docs↔API↔reality reconciliation, and PR review hygiene. Pairs with the recoup-internal-dev-issue-tracker skill (which writes the issue this one implements). +description: INTERNAL — Recoup staff tooling, gated by the recoup-internal keyword. Invoke ONLY when the request explicitly includes "recoup-internal" (e.g. "recoup-internal ship issue #N"). Never use for customer-facing or artist requests. Deliver a tracked GitHub issue end-to-end in the Recoup house style — documentation-driven (OpenAPI/contract first), then test-driven (red→green) implementation that matches the docs, verified against the live preview deployment, with results posted to the PR. Use when the user says "implement this issue", "build out issue #N", "ship the issue", "do the implementation", "take this issue and build it", or hands you a tracking issue (usually from the recoup-internal-dev-issue-tracker skill) to deliver. Covers docs-first ordering, the TDD loop, preview verification, the docs↔API↔reality reconciliation, and PR review hygiene. Pairs with the recoup-internal-dev-issue-tracker skill (which writes the issue this one implements). --- # Implementing a Tracked Issue -How we take a tracking issue from open to shipped at Recoupable. The bar: **the docs, the API, and the live preview all tell the same story — and every claim you make on the PR is something you actually ran.** No contract drift, no "should work," no untested assertions. +How we take a tracking issue from open to shipped at Recoup. The bar: **the docs, the API, and the live preview all tell the same story — and every claim you make on the PR is something you actually ran.** No contract drift, no "should work," no untested assertions. This is the delivery counterpart to the **recoup-internal-dev-issue-tracker** skill: that one writes the issue; this one implements it. The gold-standard reference slice this skill is built from is [chat#1789](https://github.com/recoupable/chat/issues/1789) (the tracking issue) → [docs#236](https://github.com/recoupable/docs/pull/236) (contract) → [api#653](https://github.com/recoupable/api/pull/653) (implementation). Read those three together to see the whole loop. @@ -45,7 +45,7 @@ Documentation-driven development: the docs/OpenAPI change is the contract, and i ## 3. API by TDD, matching the docs -Branch per the repo's rule (Recoupable `api` PRs target `test`). Mirror the sibling implementation's layering (route → handler → validate → data function → response shaping; auth; credits). +Branch per the repo's rule (Recoup `api` PRs target `test`). Mirror the sibling implementation's layering (route → handler → validate → data function → response shaping; auth; credits). **Red → green → refactor, one unit at a time:** @@ -89,7 +89,7 @@ All three must agree before you call it done. This step is the entire point of t ## 8. Hand off in merge order - Merge order is **docs → api** (the contract lands first). Honor hard dependencies (a database migration before the api that reads it). -- **Never merge without explicit user approval.** When the user approves: merge, then promote per the repo flow (Recoupable: squash → `test`, then `test` → `main` via a release PR, then sync `test` with `main`), checking the release scope first. +- **Never merge without explicit user approval.** When the user approves: merge, then promote per the repo flow (Recoup: squash → `test`, then `test` → `main` via a release PR, then sync `test` with `main`), checking the release scope first. - On merge, **update the tracking issue to Done** with the **recoup-internal-dev-issue-tracker** skill — a closure note (PR links, ✅ ISO date, merge path, what shipped, and a Verified clause citing the live results), and check off the Done-when boxes you actually verified. ## Principles diff --git a/skills/recoup-platform-api-access/SKILL.md b/skills/recoup-platform-api-access/SKILL.md index d745848..a963a16 100644 --- a/skills/recoup-platform-api-access/SKILL.md +++ b/skills/recoup-platform-api-access/SKILL.md @@ -1,11 +1,11 @@ --- name: recoup-platform-api-access -description: Call the Recoupable API and external connectors directly — fetch any platform resource (artists, socials, organizations, research, documents) and run connector actions (Google Docs/Sheets/Drive edits, Gmail, TikTok, Instagram). Use whenever you need raw Recoup data, a platform resource, to write curl against api.recoupable.com, or to read/write something outside Recoup like a Google Doc URL or a spreadsheet. The plumbing every other skill rides on. To onboard or operate on an artist use the recoup-roster-* skills; for first-run connection use recoup-platform-connect-account. +description: Call the Recoup API and external connectors directly — fetch any platform resource (artists, socials, organizations, research, documents) and run connector actions (Google Docs/Sheets/Drive edits, Gmail, TikTok, Instagram). Use whenever you need raw Recoup data, a platform resource, to write curl against api.recoupable.com, or to read/write something outside Recoup like a Google Doc URL or a spreadsheet. The plumbing every other skill rides on. To onboard or operate on an artist use the recoup-roster-* skills; for first-run connection use recoup-platform-connect-account. --- # Recoup — API Access -The platform access layer: authenticate, talk to the Recoupable REST API, and +The platform access layer: authenticate, talk to the Recoup REST API, and invoke external connectors. Base `https://api.recoupable.com/api`; docs `https://developers.recoupable.com` (`/llms.txt`, `/llms-full.txt`, OpenAPI JSONs). From 0a10e58129514d40457d88b2124ab82dccf18c63 Mon Sep 17 00:00:00 2001 From: Sidney Swift <158200036+sidneyswift@users.noreply.github.com> Date: Tue, 23 Jun 2026 02:42:41 -0400 Subject: [PATCH 3/5] fix(review): address cubic PR findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scripts/run_resolver_eval.py: LLM-tier router system prompt referenced the old "recoup-records plugin" → "Recoup Skills plugin" (cubic #1). - resolver-eval.jsonl: add adversarial gating negatives so the eval defends the invariant that internal skills must NOT fire without the recoup-internal keyword (de-gated valuation/caption/overview requests route to the customer skill, not the internal one). The dev/eval internal skills have no customer-request surface, noted inline (cubic #2). - README.md: reword the Manual install so it matches the flat-plugin architecture (the repo root is the plugin) instead of "point your agent at skills/" (cubic #3). - hooks/check-env.sh: same stale-"recoup-records" class as cubic #1 — updated the SessionStart directive to "Recoup Skills". All five validators still exit 0 (eval: 40 positives + 10 adversarial negatives). Co-Authored-By: Claude Opus 4.8 --- README.md | 2 +- hooks/check-env.sh | 6 +++--- resolver-eval.jsonl | 7 +++++++ scripts/run_resolver_eval.py | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c4c30c9..32f72b0 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ npx skills add recoupable/skills ### Manual -Clone and point your agent at the `skills/` directory: +Clone the repo and load it as a plugin — the repo root is the plugin, with every skill under `skills/`: ```bash git clone https://github.com/recoupable/skills.git diff --git a/hooks/check-env.sh b/hooks/check-env.sh index 92f57d0..4f923a1 100755 --- a/hooks/check-env.sh +++ b/hooks/check-env.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# check-env.sh — SessionStart directive for the recoup-records bundle. +# check-env.sh — SessionStart directive for the Recoup Skills plugin. # # Emits ONE imperative, position-pinned directive (not passive context the model # silently absorbs and then ignores): a session-start hook must DIRECT, not @@ -27,7 +27,7 @@ if ! command -v ffmpeg >/dev/null 2>&1; then ffmpeg_note=" (note: ffmpeg not on PATH — the async content pipeline still works; only the short-video manual compose/mux step needs it.)" fi -echo "[SESSION-START DIRECTIVE — recoup-records]" +echo "[SESSION-START DIRECTIVE — Recoup Skills]" if [ "$have_cred" = "no" ]; then # Single directive: nothing else works without a credential, so this wins. @@ -37,7 +37,7 @@ if [ "$have_cred" = "no" ]; then echo "route every request through the bundle's RESOLVER.md.${ffmpeg_note}" else # Single directive: routing. Picking the right skill is the job. - echo "DIRECTIVE: Before acting on any request, consult the recoup-records" + echo "DIRECTIVE: Before acting on any request, consult the" echo "RESOLVER.md (the skill dispatcher at the plugin root): match the request to" echo "one skill, then READ that skill's SKILL.md before doing the work. If two" echo "skills could match, read both and pick the narrower. Do not improvise a" diff --git a/resolver-eval.jsonl b/resolver-eval.jsonl index b262b60..357c2b7 100644 --- a/resolver-eval.jsonl +++ b/resolver-eval.jsonl @@ -55,3 +55,10 @@ {"intent": "recoup-internal work the valuation funnel and advance this Attio lead", "expected": "recoup-internal-funnel-valuation-pipeline"} {"intent": "recoup-internal account health snapshot for this account", "expected": "recoup-internal-account-health-report"} {"intent": "recoup-internal draft and ship a launch post for LinkedIn and X", "expected": "recoup-internal-social-ship-posts"} + +# --- gating invariant: WITHOUT the recoup-internal keyword, internal skills must NOT fire --- +# Only the internal skills with a plausible customer-facing collision get a negative; the dev/eval +# skills (issue-tracker, ship-issue, eval-skill-benchmark) have no customer-request surface to confuse. +{"intent": "what's this catalog worth from public streaming data", "expected": "recoup-catalog-estimate-value", "not": ["recoup-internal-funnel-valuation-pipeline"]} +{"intent": "write a caption for the artist's instagram post", "expected": "recoup-content-write-caption", "not": ["recoup-internal-social-ship-posts"]} +{"intent": "how is this artist doing across streaming and socials", "expected": "recoup-research-artist-overview", "not": ["recoup-internal-account-health-report"]} diff --git a/scripts/run_resolver_eval.py b/scripts/run_resolver_eval.py index b635b36..3b53cea 100644 --- a/scripts/run_resolver_eval.py +++ b/scripts/run_resolver_eval.py @@ -134,7 +134,7 @@ def skill_descriptions() -> dict[str, str]: def _call_gateway(intent: str, catalog: str) -> str: """Route one intent via the gateway. Returns the raw reply (or '__ERROR__: ...').""" system = ( - "You are the skill router for the recoup-records plugin. Given a USER " + "You are the skill router for the Recoup Skills plugin. Given a USER " "REQUEST and a catalog of skills (name: description), choose the SINGLE " "best skill. Reply with ONLY the skill name (e.g. recoup-research); reply " "none if nothing fits." From 3590eb500f3770a0c309f360eabec77b499434f1 Mon Sep 17 00:00:00 2001 From: Sidney Swift <158200036+sidneyswift@users.noreply.github.com> Date: Tue, 23 Jun 2026 02:51:52 -0400 Subject: [PATCH 4/5] chore(vendoring): wire vendored.json to the shared files it should guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After the flatten, check_vendored was a no-op (0 groups) — but the repo still vendors heavily: per the Portable Skill Contract each skill is self-contained, so references/templates/fixtures shared by multiple skills are copied into each skill. Those copies (workflows.md ×6, the content references ×5 each, flamingo-api.md ×3, the deal templates, the demo data room, …) were unregistered and could silently drift. - scripts/vendored.json: register all cross-skill / skill↔root duplicates — 24 groups (1 dir: fixtures/demo-data-room; 23 file), 62 copies. Canonical = the repo-root references/ , templates/ , fixtures/ copy (or one skill copy where no root copy exists). - AGENTS.md: drop the now-stale "vendored.json is currently empty" note from the glossary. check_vendored now verifies 62 copies across 24 groups; all five gates exit 0. Co-Authored-By: Claude Opus 4.8 --- AGENTS.md | 2 +- scripts/vendored.json | 187 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 186 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e31ccca..07f6170 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,7 @@ recoupable/skills/ ← the repo root IS the plugin - **Plugin** — this repo, rooted at the repo root: it ships every skill in `skills/` **plus** agents, hooks, and shared references, installed through a runtime's plugin system or `npx skills`. A skill is a subset of the plugin. (No slash-`commands/` — skills only; see "No slash-commands".) - **Harness** — a runtime that loads skills/plugins: Claude Code, Codex, Cursor, or bare `npx skills`. - **Marketplace registry** — the single installable-plugin entry (`recoup-skills`, `source "."`), written in `.claude-plugin/marketplace.json` and `.agents/plugins/marketplace.json`. -- **Canonical / vendored** — when two places need the same file, one copy is the *canonical* source and the rest are byte-identical *vendored* copies tracked in `scripts/vendored.json` (currently empty — no shared copies today). +- **Canonical / vendored** — when two places need the same file, one copy is the *canonical* source and the rest are byte-identical *vendored* copies tracked in `scripts/vendored.json` (the references, templates, and fixtures shared across skills). - **Resolver** — the repo-root `RESOLVER.md` routing table that maps a user request to the one skill that should handle it. Skills stay flat and discoverable; there is no separate "router" entry-point skill. Reachability is enforced by `scripts/check_resolvable.py`. ## How Skills Load diff --git a/scripts/vendored.json b/scripts/vendored.json index a607863..421e625 100644 --- a/scripts/vendored.json +++ b/scripts/vendored.json @@ -1,4 +1,187 @@ { - "_comment": "Maps each canonical shared file to its byte-identical vendored copies, kept in sync by scripts/check_vendored.py. See AGENTS.md 'Portable Skill Contract'. After the collapse into a single flat plugin at the repo root there are no cross-plugin vendored copies; each skill vendors its own references inside its own directory. This list is intentionally empty.", - "groups": [] + "_comment": "Maps each canonical shared file/dir to its byte-identical vendored copies, kept in sync by scripts/check_vendored.py. See AGENTS.md 'Portable Skill Contract' rule 5: skills are self-contained, so references/templates/fixtures shared by multiple skills are COPIED into each skill (not centralized) and registered here so they can't silently drift. Canonical lives at the repo root (references/ , templates/ , fixtures/); copies live inside each skill's own dir. Edit the canonical, then re-sync per AGENTS.md.", + "groups": [ + { + "canonical_dir": "fixtures/demo-data-room", + "copies_dirs": [ + "skills/recoup-catalog-review-deal/fixtures/demo-data-room" + ] + }, + { + "canonical": "references/account-resolver.md", + "copies": [ + "skills/recoup-content-asset-pack/references/account-resolver.md", + "skills/recoup-content-make-graphics/references/account-resolver.md", + "skills/recoup-content-make-video/references/account-resolver.md", + "skills/recoup-content-reactive-post/references/account-resolver.md", + "skills/recoup-content-write-caption/references/account-resolver.md" + ] + }, + { + "canonical": "references/analyze-gate.md", + "copies": [ + "skills/recoup-content-asset-pack/references/analyze-gate.md", + "skills/recoup-content-make-graphics/references/analyze-gate.md", + "skills/recoup-content-make-video/references/analyze-gate.md", + "skills/recoup-content-reactive-post/references/analyze-gate.md", + "skills/recoup-content-write-caption/references/analyze-gate.md" + ] + }, + { + "canonical": "references/content-api.md", + "copies": [ + "skills/recoup-content-asset-pack/references/content-api.md", + "skills/recoup-content-make-graphics/references/content-api.md", + "skills/recoup-content-make-video/references/content-api.md", + "skills/recoup-content-reactive-post/references/content-api.md", + "skills/recoup-content-write-caption/references/content-api.md" + ] + }, + { + "canonical": "references/deal-workflow.md", + "copies": [ + "skills/recoup-catalog-review-deal/references/deal-workflow.md" + ] + }, + { + "canonical": "references/deal-workspace.md", + "copies": [ + "skills/recoup-catalog-review-deal/references/deal-workspace.md" + ] + }, + { + "canonical": "references/endpoints.md", + "copies": [ + "skills/recoup-research-artist-overview/references/endpoints.md" + ] + }, + { + "canonical": "references/flamingo-api.md", + "copies": [ + "skills/recoup-song-analyze-audio/references/flamingo-api.md", + "skills/recoup-song-find-hook/references/flamingo-api.md", + "skills/recoup-song-placement-pitch/references/flamingo-api.md" + ] + }, + { + "canonical": "references/red-flags.md", + "copies": [ + "skills/recoup-catalog-review-deal/references/red-flags.md" + ] + }, + { + "canonical": "references/research-context.md", + "copies": [ + "skills/recoup-content-asset-pack/references/research-context.md", + "skills/recoup-content-make-graphics/references/research-context.md", + "skills/recoup-content-make-video/references/research-context.md", + "skills/recoup-content-reactive-post/references/research-context.md", + "skills/recoup-content-write-caption/references/research-context.md" + ] + }, + { + "canonical": "references/response-shapes.md", + "copies": [ + "skills/recoup-research-artist-overview/references/response-shapes.md" + ] + }, + { + "canonical": "references/song-sourcing.md", + "copies": [ + "skills/recoup-content-asset-pack/references/song-sourcing.md", + "skills/recoup-content-make-graphics/references/song-sourcing.md", + "skills/recoup-content-make-video/references/song-sourcing.md", + "skills/recoup-content-reactive-post/references/song-sourcing.md", + "skills/recoup-content-write-caption/references/song-sourcing.md" + ] + }, + { + "canonical": "references/workflows.md", + "copies": [ + "skills/recoup-research-artist-overview/references/workflows.md", + "skills/recoup-research-find-contacts/references/workflows.md", + "skills/recoup-research-find-talent/references/workflows.md", + "skills/recoup-research-playlist-targets/references/workflows.md", + "skills/recoup-research-the-web/references/workflows.md", + "skills/recoup-research-weekly-brief/references/workflows.md" + ] + }, + { + "canonical": "references/workspace-context.md", + "copies": [ + "skills/recoup-content-asset-pack/references/workspace-context.md", + "skills/recoup-content-make-graphics/references/workspace-context.md", + "skills/recoup-content-make-video/references/workspace-context.md", + "skills/recoup-content-reactive-post/references/workspace-context.md", + "skills/recoup-content-write-caption/references/workspace-context.md" + ] + }, + { + "canonical": "skills/recoup-catalog-estimate-value/references/methodology.md", + "copies": [ + "skills/recoup-catalog-review-deal/references/methodology.md" + ] + }, + { + "canonical": "skills/recoup-catalog-estimate-value/references/recoup-api.md", + "copies": [ + "skills/recoup-catalog-review-deal/references/recoup-api.md" + ] + }, + { + "canonical": "skills/recoup-release-plan-rollout/references/response-shapes.md", + "copies": [ + "skills/recoup-release-track-drop/references/response-shapes.md" + ] + }, + { + "canonical": "templates/deal-workspace/assumptions.yaml", + "copies": [ + "skills/recoup-catalog-review-deal/templates/assumptions.yaml", + "skills/recoup-catalog-review-deal/templates/deal-workspace/assumptions.yaml" + ] + }, + { + "canonical": "templates/deal-workspace/evidence-ledger.json", + "copies": [ + "skills/recoup-catalog-review-deal/templates/deal-workspace/evidence-ledger.json", + "skills/recoup-catalog-review-deal/templates/evidence-ledger.json" + ] + }, + { + "canonical": "templates/deal-workspace/findings.json", + "copies": [ + "skills/recoup-catalog-review-deal/templates/deal-workspace/findings.json", + "skills/recoup-catalog-review-deal/templates/findings.json" + ] + }, + { + "canonical": "templates/deal-workspace/memos/financing-pack.md", + "copies": [ + "skills/recoup-catalog-review-deal/templates/deal-workspace/memos/financing-pack.md", + "skills/recoup-catalog-review-deal/templates/memos/financing-pack.md" + ] + }, + { + "canonical": "templates/deal-workspace/memos/ic-memo.md", + "copies": [ + "skills/recoup-catalog-review-deal/templates/deal-workspace/memos/ic-memo.md", + "skills/recoup-catalog-review-deal/templates/memos/ic-memo.md" + ] + }, + { + "canonical": "templates/deal-workspace/memos/seller-cleanup-report.md", + "copies": [ + "skills/recoup-catalog-review-deal/templates/deal-workspace/memos/seller-cleanup-report.md", + "skills/recoup-catalog-review-deal/templates/memos/seller-cleanup-report.md" + ] + }, + { + "canonical": "templates/deal-workspace/missing-files.md", + "copies": [ + "skills/recoup-catalog-review-deal/templates/deal-workspace/missing-files.md", + "skills/recoup-catalog-review-deal/templates/missing-files.md" + ] + } + ] } From 9439f6799988051cf006f20e18eaca1870853957 Mon Sep 17 00:00:00 2001 From: Sidney Swift <158200036+sidneyswift@users.noreply.github.com> Date: Tue, 23 Jun 2026 03:07:14 -0400 Subject: [PATCH 5/5] docs(vendoring): correct vendored.json _comment re: canonical location MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cubic flagged that the _comment claimed all canonicals live at the repo root, but 3 of 24 groups have skill-internal canonicals — files shared only between sibling skills with no root copy (methodology.md, recoup-api.md, and the release variant of response-shapes.md, which can't sit at references/response-shapes.md since that name already holds the different research variant). Hoisting isn't possible/desirable, so align the docs with reality instead: - scripts/vendored.json: _comment now says canonical is the root copy where the file exists at root, else one skill copy for sibling-only shared files. - AGENTS.md: same softening of the "root is always canonical" line. No group data changed; check_vendored still passes (62 copies / 24 groups). Co-Authored-By: Claude Opus 4.8 --- AGENTS.md | 2 +- scripts/vendored.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 07f6170..7dd978b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -95,7 +95,7 @@ The whole repo is one flat plugin. There is **no `plugins/` directory** and no p - **Add a skill** by creating `skills/recoup-[domain]-[verb]-[noun]/SKILL.md` (see "Skill Format"), then add a route in `RESOLVER.md` and a fixture in `resolver-eval.jsonl`. CI fails on any skill that isn't reachable from the resolver. - **Manifests live at the repo root** — `.claude-plugin/plugin.json` (Claude), `.codex-plugin/plugin.json` (Codex), and the two `marketplace.json` files. Skills auto-resolve from `skills/`; you don't enumerate them in the manifest. -- **Shared components sit at the root** too: `agents/`, `hooks/`, `references/`, `templates/`, `fixtures/`. A skill must still be **self-contained** — it may reference only files inside its own directory, so a skill that needs a shared reference vendors its own copy (Portable Skill Contract rule 5). The root `references/` is the canonical source for any such copies and is also used by `agents/` and `hooks/`. +- **Shared components sit at the root** too: `agents/`, `hooks/`, `references/`, `templates/`, `fixtures/`. A skill must still be **self-contained** — it may reference only files inside its own directory, so a skill that needs a shared reference vendors its own copy (Portable Skill Contract rule 5). The root `references/`/`templates/`/`fixtures/` hold the canonical copy for most shared files (a few shared only between sibling skills keep their canonical inside one skill); these roots are also used by `agents/` and `hooks/`. - Every skill follows the **Portable Skill Contract** below. ## No slash-commands — skills only diff --git a/scripts/vendored.json b/scripts/vendored.json index 421e625..0497763 100644 --- a/scripts/vendored.json +++ b/scripts/vendored.json @@ -1,5 +1,5 @@ { - "_comment": "Maps each canonical shared file/dir to its byte-identical vendored copies, kept in sync by scripts/check_vendored.py. See AGENTS.md 'Portable Skill Contract' rule 5: skills are self-contained, so references/templates/fixtures shared by multiple skills are COPIED into each skill (not centralized) and registered here so they can't silently drift. Canonical lives at the repo root (references/ , templates/ , fixtures/); copies live inside each skill's own dir. Edit the canonical, then re-sync per AGENTS.md.", + "_comment": "Maps each canonical shared file/dir to its byte-identical vendored copies, kept in sync by scripts/check_vendored.py. See AGENTS.md 'Portable Skill Contract' rule 5: skills are self-contained, so references/templates/fixtures shared by multiple skills are COPIED into each skill (not centralized) and registered here so they can't silently drift. The canonical is the repo-root copy (references/ , templates/ , fixtures/) when the shared file exists at root; for the few files shared only between sibling skills (no root copy), the canonical is one skill's copy and the others point at it. Edit the canonical, then re-sync per AGENTS.md.", "groups": [ { "canonical_dir": "fixtures/demo-data-room",